Signed-off-by: Tibor Vass <teabee89@gmail.com>
| ... | ... |
@@ -39,6 +39,7 @@ import ( |
| 39 | 39 |
"github.com/docker/docker/pkg/parsers/filters" |
| 40 | 40 |
"github.com/docker/docker/pkg/promise" |
| 41 | 41 |
"github.com/docker/docker/pkg/signal" |
| 42 |
+ "github.com/docker/docker/pkg/symlink" |
|
| 42 | 43 |
"github.com/docker/docker/pkg/term" |
| 43 | 44 |
"github.com/docker/docker/pkg/timeutils" |
| 44 | 45 |
"github.com/docker/docker/pkg/units" |
| ... | ... |
@@ -147,36 +148,36 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 147 | 147 |
return err |
| 148 | 148 |
} |
| 149 | 149 |
|
| 150 |
- var filename string // path to Dockerfile |
|
| 151 |
- var origDockerfile string // used for error msg |
|
| 150 |
+ filename := *dockerfileName // path to Dockerfile |
|
| 152 | 151 |
|
| 153 | 152 |
if *dockerfileName == "" {
|
| 154 | 153 |
// No -f/--file was specified so use the default |
| 155 |
- origDockerfile = api.DefaultDockerfileName |
|
| 156 |
- *dockerfileName = origDockerfile |
|
| 154 |
+ *dockerfileName = api.DefaultDockerfileName |
|
| 157 | 155 |
filename = path.Join(absRoot, *dockerfileName) |
| 158 |
- } else {
|
|
| 159 |
- origDockerfile = *dockerfileName |
|
| 160 |
- if filename, err = filepath.Abs(*dockerfileName); err != nil {
|
|
| 161 |
- return err |
|
| 162 |
- } |
|
| 156 |
+ } |
|
| 163 | 157 |
|
| 164 |
- // Verify that 'filename' is within the build context |
|
| 165 |
- if !strings.HasSuffix(absRoot, string(os.PathSeparator)) {
|
|
| 166 |
- absRoot += string(os.PathSeparator) |
|
| 167 |
- } |
|
| 168 |
- if !strings.HasPrefix(filename, absRoot) {
|
|
| 169 |
- return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", *dockerfileName, root)
|
|
| 170 |
- } |
|
| 158 |
+ origDockerfile := *dockerfileName // used for error msg |
|
| 159 |
+ |
|
| 160 |
+ if filename, err = filepath.Abs(filename); err != nil {
|
|
| 161 |
+ return err |
|
| 162 |
+ } |
|
| 171 | 163 |
|
| 172 |
- // Now reset the dockerfileName to be relative to the build context |
|
| 173 |
- *dockerfileName = filename[len(absRoot):] |
|
| 164 |
+ // Verify that 'filename' is within the build context |
|
| 165 |
+ filename, err = symlink.FollowSymlinkInScope(filename, absRoot) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root)
|
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // Now reset the dockerfileName to be relative to the build context |
|
| 171 |
+ *dockerfileName, err = filepath.Rel(filename, absRoot) |
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ return err |
|
| 174 | 174 |
} |
| 175 | 175 |
|
| 176 |
- if _, err = os.Stat(filename); os.IsNotExist(err) {
|
|
| 177 |
- return fmt.Errorf("Can not locate Dockerfile: %s", origDockerfile)
|
|
| 176 |
+ if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
|
| 177 |
+ return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
|
|
| 178 | 178 |
} |
| 179 |
- var includes []string = []string{"."}
|
|
| 179 |
+ var includes = []string{"."}
|
|
| 180 | 180 |
|
| 181 | 181 |
excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) |
| 182 | 182 |
if err != nil {
|
| ... | ... |
@@ -32,6 +32,7 @@ import ( |
| 32 | 32 |
"github.com/docker/docker/daemon" |
| 33 | 33 |
"github.com/docker/docker/engine" |
| 34 | 34 |
"github.com/docker/docker/pkg/fileutils" |
| 35 |
+ "github.com/docker/docker/pkg/symlink" |
|
| 35 | 36 |
"github.com/docker/docker/pkg/tarsum" |
| 36 | 37 |
"github.com/docker/docker/registry" |
| 37 | 38 |
"github.com/docker/docker/runconfig" |
| ... | ... |
@@ -170,16 +171,12 @@ func (b *Builder) Run(context io.Reader) (string, error) {
|
| 170 | 170 |
// Reads a Dockerfile from the current context. It assumes that the |
| 171 | 171 |
// 'filename' is a relative path from the root of the context |
| 172 | 172 |
func (b *Builder) readDockerfile(origFile string) error {
|
| 173 |
- filename := filepath.Join(b.contextPath, origFile) |
|
| 174 |
- |
|
| 175 |
- tmpDockerPath := filepath.Dir(filename) + string(os.PathSeparator) |
|
| 176 |
- tmpContextPath := filepath.Clean(b.contextPath) + string(os.PathSeparator) |
|
| 177 |
- |
|
| 178 |
- if !strings.HasPrefix(tmpDockerPath, tmpContextPath) {
|
|
| 179 |
- return fmt.Errorf("Dockerfile (%s) must be within the build context", origFile)
|
|
| 173 |
+ filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath) |
|
| 174 |
+ if err != nil {
|
|
| 175 |
+ return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
|
|
| 180 | 176 |
} |
| 181 | 177 |
|
| 182 |
- fi, err := os.Stat(filename) |
|
| 178 |
+ fi, err := os.Lstat(filename) |
|
| 183 | 179 |
if os.IsNotExist(err) {
|
| 184 | 180 |
return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
|
| 185 | 181 |
} |
| ... | ... |
@@ -307,13 +307,14 @@ func TestBuildApiDockerfilePath(t *testing.T) {
|
| 307 | 307 |
tw := tar.NewWriter(buffer) |
| 308 | 308 |
defer tw.Close() |
| 309 | 309 |
|
| 310 |
+ dockerfile := []byte("FROM busybox")
|
|
| 310 | 311 |
if err := tw.WriteHeader(&tar.Header{
|
| 311 | 312 |
Name: "Dockerfile", |
| 312 |
- Size: 11, |
|
| 313 |
+ Size: int64(len(dockerfile)), |
|
| 313 | 314 |
}); err != nil {
|
| 314 | 315 |
t.Fatalf("failed to write tar file header: %v", err)
|
| 315 | 316 |
} |
| 316 |
- if _, err := tw.Write([]byte("FROM ubuntu")); err != nil {
|
|
| 317 |
+ if _, err := tw.Write(dockerfile); err != nil {
|
|
| 317 | 318 |
t.Fatalf("failed to write tar file content: %v", err)
|
| 318 | 319 |
} |
| 319 | 320 |
if err := tw.Close(); err != nil {
|
| ... | ... |
@@ -322,12 +323,46 @@ func TestBuildApiDockerfilePath(t *testing.T) {
|
| 322 | 322 |
|
| 323 | 323 |
out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
|
| 324 | 324 |
if err == nil {
|
| 325 |
- t.Fatalf("Build was supposed to fail")
|
|
| 325 |
+ t.Fatalf("Build was supposed to fail: %s", out)
|
|
| 326 | 326 |
} |
| 327 | 327 |
|
| 328 | 328 |
if !strings.Contains(string(out), "must be within the build context") {
|
| 329 |
- t.Fatalf("Didn't complain about leaving build context")
|
|
| 329 |
+ t.Fatalf("Didn't complain about leaving build context: %s", out)
|
|
| 330 | 330 |
} |
| 331 | 331 |
|
| 332 | 332 |
logDone("container REST API - check build w/bad Dockerfile path")
|
| 333 | 333 |
} |
| 334 |
+ |
|
| 335 |
+func TestBuildApiDockerfileSymlink(t *testing.T) {
|
|
| 336 |
+ // Test to make sure we stop people from trying to leave the |
|
| 337 |
+ // build context when specifying a symlink as the path to the dockerfile |
|
| 338 |
+ buffer := new(bytes.Buffer) |
|
| 339 |
+ tw := tar.NewWriter(buffer) |
|
| 340 |
+ defer tw.Close() |
|
| 341 |
+ |
|
| 342 |
+ if err := tw.WriteHeader(&tar.Header{
|
|
| 343 |
+ Name: "Dockerfile", |
|
| 344 |
+ Typeflag: tar.TypeSymlink, |
|
| 345 |
+ Linkname: "/etc/passwd", |
|
| 346 |
+ }); err != nil {
|
|
| 347 |
+ t.Fatalf("failed to write tar file header: %v", err)
|
|
| 348 |
+ } |
|
| 349 |
+ if err := tw.Close(); err != nil {
|
|
| 350 |
+ t.Fatalf("failed to close tar archive: %v", err)
|
|
| 351 |
+ } |
|
| 352 |
+ |
|
| 353 |
+ out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
|
|
| 354 |
+ if err == nil {
|
|
| 355 |
+ t.Fatalf("Build was supposed to fail: %s", out)
|
|
| 356 |
+ } |
|
| 357 |
+ |
|
| 358 |
+ // The reason the error is "Cannot locate specified Dockerfile" is because |
|
| 359 |
+ // in the builder, the symlink is resolved within the context, therefore |
|
| 360 |
+ // Dockerfile -> /etc/passwd becomes etc/passwd from the context which is |
|
| 361 |
+ // a nonexistent file. |
|
| 362 |
+ if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") {
|
|
| 363 |
+ t.Fatalf("Didn't complain about leaving build context: %s", out)
|
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ logDone("container REST API - check build w/bad Dockerfile symlink path")
|
|
| 367 |
+} |
| ... | ... |
@@ -4630,3 +4630,67 @@ func TestBuildFromOfficialNames(t *testing.T) {
|
| 4630 | 4630 |
} |
| 4631 | 4631 |
logDone("build - from official names")
|
| 4632 | 4632 |
} |
| 4633 |
+ |
|
| 4634 |
+func TestBuildDockerfileOutsideContext(t *testing.T) {
|
|
| 4635 |
+ name := "testbuilddockerfileoutsidecontext" |
|
| 4636 |
+ tmpdir, err := ioutil.TempDir("", name)
|
|
| 4637 |
+ if err != nil {
|
|
| 4638 |
+ t.Fatal(err) |
|
| 4639 |
+ } |
|
| 4640 |
+ defer os.RemoveAll(tmpdir) |
|
| 4641 |
+ ctx := filepath.Join(tmpdir, "context") |
|
| 4642 |
+ if err := os.MkdirAll(ctx, 0755); err != nil {
|
|
| 4643 |
+ t.Fatal(err) |
|
| 4644 |
+ } |
|
| 4645 |
+ if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte("FROM busybox"), 0644); err != nil {
|
|
| 4646 |
+ t.Fatal(err) |
|
| 4647 |
+ } |
|
| 4648 |
+ wd, err := os.Getwd() |
|
| 4649 |
+ if err != nil {
|
|
| 4650 |
+ t.Fatal(err) |
|
| 4651 |
+ } |
|
| 4652 |
+ defer os.Chdir(wd) |
|
| 4653 |
+ if err := os.Chdir(ctx); err != nil {
|
|
| 4654 |
+ t.Fatal(err) |
|
| 4655 |
+ } |
|
| 4656 |
+ if err := ioutil.WriteFile(filepath.Join(tmpdir, "outsideDockerfile"), []byte("FROM busbox"), 0644); err != nil {
|
|
| 4657 |
+ t.Fatal(err) |
|
| 4658 |
+ } |
|
| 4659 |
+ if err := os.Symlink("../outsideDockerfile", filepath.Join(ctx, "dockerfile1")); err != nil {
|
|
| 4660 |
+ t.Fatal(err) |
|
| 4661 |
+ } |
|
| 4662 |
+ if err := os.Symlink(filepath.Join(tmpdir, "outsideDockerfile"), filepath.Join(ctx, "dockerfile2")); err != nil {
|
|
| 4663 |
+ t.Fatal(err) |
|
| 4664 |
+ } |
|
| 4665 |
+ if err := os.Link("../outsideDockerfile", filepath.Join(ctx, "dockerfile3")); err != nil {
|
|
| 4666 |
+ t.Fatal(err) |
|
| 4667 |
+ } |
|
| 4668 |
+ if err := os.Link(filepath.Join(tmpdir, "outsideDockerfile"), filepath.Join(ctx, "dockerfile4")); err != nil {
|
|
| 4669 |
+ t.Fatal(err) |
|
| 4670 |
+ } |
|
| 4671 |
+ for _, dockerfilePath := range []string{
|
|
| 4672 |
+ "../outsideDockerfile", |
|
| 4673 |
+ filepath.Join(ctx, "dockerfile1"), |
|
| 4674 |
+ filepath.Join(ctx, "dockerfile2"), |
|
| 4675 |
+ filepath.Join(ctx, "dockerfile3"), |
|
| 4676 |
+ filepath.Join(ctx, "dockerfile4"), |
|
| 4677 |
+ } {
|
|
| 4678 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", dockerfilePath, ".")) |
|
| 4679 |
+ if err == nil {
|
|
| 4680 |
+ t.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out)
|
|
| 4681 |
+ } |
|
| 4682 |
+ deleteImages(name) |
|
| 4683 |
+ } |
|
| 4684 |
+ |
|
| 4685 |
+ os.Chdir(tmpdir) |
|
| 4686 |
+ |
|
| 4687 |
+ // Path to Dockerfile should be resolved relative to working directory, not relative to context. |
|
| 4688 |
+ // There is a Dockerfile in the context, but since there is no Dockerfile in the current directory, the following should fail |
|
| 4689 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", "Dockerfile", ctx)) |
|
| 4690 |
+ if err == nil {
|
|
| 4691 |
+ t.Fatalf("Expected error. Out: %s", out)
|
|
| 4692 |
+ } |
|
| 4693 |
+ deleteImages(name) |
|
| 4694 |
+ |
|
| 4695 |
+ logDone("build - Dockerfile outside context")
|
|
| 4696 |
+} |