-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
address interpolation of artifact and template src/dest #9671
Conversation
0962139
to
1215201
Compare
1215201
to
d286454
Compare
// If the interpolated result is a relative path, it is made absolute | ||
// wrt to the task working directory. | ||
// The result is checked to see whether it escapes both the task working | ||
// directory and the shared allocation directory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this the result of the refactoring from #9668... it is potentially excessive refactoring. this method provides the functionality that we need for template and artifact path interpolation. namely, using the client-relative environment values to interpolate paths that we're using on the client: paths for consul-template and go-getter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whatever we can do to make it a "no brainer" for folks trying to handle client paths safely seems like a good thing to me. So I don't feel like it's excessive to pull this out to a centralized implementation! 😀
nodeAttrs := make(map[string]string) | ||
// buildEnv returns the environment variables and device environment | ||
// variables with respect to the task directories passed in the arguments. | ||
func (b *Builder) buildEnv(allocDir, localDir, secretsDir string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there was a lot of interpolation that happened while creating these environment variables, so i did a back-of-the-envelope dependency analysis and broke out the code for creating the env vars and the device env vars into this method.
@@ -93,11 +100,10 @@ func setEnvvars(envBuilder *taskenv.Builder, fsi drivers.FSIsolation, taskDir *a | |||
// Set the host environment variables for non-image based drivers | |||
if fsi != drivers.FSIsolationImage { | |||
// COMPAT(1.0) using inclusive language, blacklist is kept for backward compatibility. | |||
denylist := conf.ReadAlternativeDefault( | |||
filter := strings.Split(conf.ReadAlternativeDefault( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just making this consistent with its clone in plugins:
https://github.com/hashicorp/nomad/blob/master/plugins/drivers/testutils/testing.go#L252
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overall approach here LGTM. I think this will be a big improvement in terms of comprehensibility for both the operators and us developers.
// If the interpolated result is a relative path, it is made absolute | ||
// wrt to the task working directory. | ||
// The result is checked to see whether it escapes both the task working | ||
// directory and the shared allocation directory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whatever we can do to make it a "no brainer" for folks trying to handle client paths safely seems like a good thing to me. So I don't feel like it's excessive to pull this out to a centralized implementation! 😀
// is supported as a destination for artifact and template blocks, and | ||
// that it is properly interpolated for task drivers with varying | ||
// filesystem isolation | ||
func (tc *ConsulTemplateTest) TestTemplatePathInterpolation_SharedAllocDir(f *framework.F) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is great 👍
client/taskenv/env.go
Outdated
envMap, deviceEnvs := b.buildEnv(b.allocDir, b.localDir, b.secretsDir, nodeAttrs) | ||
envMapClient, _ := b.buildEnv(b.clientAllocDir, b.clientLocalDir, b.clientSecretsDir, nodeAttrs) | ||
|
||
return NewTaskEnv(envMap, envMapClient, deviceEnvs, nodeAttrs, b.clientTaskDir, b.clientAllocDir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this is the only non-test caller? I'm wondering if we should make this internal to the package to force test callers to always use the full safe path?
okay, it looks like i've got a problem... there is a use case that i didn't capture in this PR which may send this back to the drawing board: it looks like the previous behavior of always joining against taskdir meant that absolute paths in the template destination would map to the same absolute path in the container: ... except for docker, where this only worked if the absolute path was one of the bind-mounted paths:
so, i need to figure out whether this invalidates the approach or not and deal with it. it may be as simple as handling absolute paths a little better... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow the hiccup you've hit. It's definitely awfully confusing as we have 3 "FSIsolation" modes that affect this:
- None (raw exec) - ClientPath == TaskPath but behavior is like Chroot (exec) in that you can write anywhere in the task's filesystem.
- Chroot (exec/java) - ClientPath != TaskPath. Runtime paths are like Image but you can write to anywhere in the task's filesystem like None (raw exec) above.
- Image (docker/podman) - ClientPath != TaskPath. Runtime paths are like Chroot but you can only write to the 3 bind-mounted paths (secrets, local, alloc).
For simplicities sake I think we should always be bind mounting <alloc-dir>/<alloc-id>/alloc
to <alloc-dir>/<alloc-id>/<task>/alloc
and doing all operations on that path. I'm not sure we ever need to reference the "real" .../alloc
.
Image drivers like Docker and Podman should have .../alloc
bind-mounted into their container filesystem as well.
I think that double bind mounting (once to create the task directory on the host, and the second time for Image based drivers) should make it so we never need to special case where the "alloc dir" is? It's the same as local
and secrets
.
While the ability to write anywhere on a Chroot driver's filesystem was likely an unintentional side effect, it should be considered a unique feature at this point distinct from how Image behaves. There's no point in trying to rememdy the discrepancy between the two now. In the future I hope Chroot goes away entirely in favor of Image, but obviously totally removing the Chroot behavior will take a long time.
We used to do this, but (if I'm understanding the PR correctly) we stopped because it required root for the Nomad client (which some docker-only users didn't need/want): |
rebased against master and re-enabled the disabled test from #9383 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used to do this, but [...] we stopped because it required root for the Nomad client (which some docker-only users didn't need/want)
Argh of course, good catch. I even recognize that face on the PR 😬
An alternate fix for #2178 would have been to soft-fail on that linkDir
call. I think that would make raw_exec
behave more reasonably when Nomad is run as root.
I think this is a better solution all around thouhg? Distinctly rendering client paths vs task paths. Great work.
// If joinEscape, an interpolated path that escapes will be joined with the | ||
// task dir. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add an example usage here? Maybe it's the holidays, but I don't want to admit how long I stared at this and the relevant 2 lines of code to figure out what was going on. 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, will do. this is the hiccup that i ran up against, where previous behavior allowed for absolute destination paths for artifact
and template
:
https://github.com/hashicorp/nomad/pull/9671/files#diff-90e77cdb4f5439161bf5a053bf2d0b74d3ac3ee65cfce482bbd8e02646a14a64L140-L144
i'll add a documenting test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func (b *Builder) SetClientSharedAllocDir(dir string) *Builder { | ||
b.mu.Lock() | ||
b.clientSharedAllocDir = dir | ||
b.mu.Unlock() | ||
return b | ||
} | ||
|
||
func (b *Builder) SetClientTaskRoot(dir string) *Builder { | ||
b.mu.Lock() | ||
b.clientTaskRoot = dir | ||
b.mu.Unlock() | ||
return b | ||
} | ||
|
||
func (b *Builder) SetClientTaskLocalDir(dir string) *Builder { | ||
b.mu.Lock() | ||
b.clientTaskLocalDir = dir | ||
b.mu.Unlock() | ||
return b | ||
} | ||
|
||
func (b *Builder) SetClientTaskSecretsDir(dir string) *Builder { | ||
b.mu.Lock() | ||
b.clientTaskSecretsDir = dir | ||
b.mu.Unlock() | ||
return b | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are reqiured for Build()
's proper function it's a shame we can't enforce those dependencies through the New func or similar, but I can see how that gets awkward given the way Builder's are built up vs where FSIsolation is handled. A problem for another time perhaps.
Co-authored-by: Michael Schurter <mschurter@hashicorp.com>
e70f08d
to
196f550
Compare
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
refactored #9668 for (imho) better abstraction
Resolves #9610
Resolves #9389
Resolves #6929
The main change is to use client-specific absolute paths in the environment maps for interpolating
source
fortemplate
anddestination
fortemplate
andartifact
.Because of this, it required adjusting the sandbox-escape checks to allow shared alloc dir, in addition to task working dir.
The result is that we should now support source and destination paths of the following forms:
${NOMAD_ALLOC_DIR}
,${NOMAD_SECRETS_DIR}
,${NOMAD_TASK_DIR}
local
,secrets
,../alloc
The new e2e test shows the capability that we're targeting.