Skip to content
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

server: handle invalid jobs in expose handler hook #10154

Merged
merged 2 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions nomad/job_endpoint_hook_expose_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ func (jobExposeCheckHook) Mutate(job *structs.Job) (_ *structs.Job, warnings []e
for _, s := range tg.Services {
for _, c := range s.Checks {
if c.Expose {
// TG isn't validated yet, but validation
// may depend on mutation results.
// Do basic validation here and skip mutation,
// so Validate can return a meaningful error
// messages
if !s.Connect.HasSidecar() {
continue
}

if exposePath, err := exposePathForCheck(tg, s, c); err != nil {
return nil, nil, err
} else if exposePath != nil {
Expand Down
2 changes: 2 additions & 0 deletions nomad/job_endpoint_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type jobValidator interface {
}

func (j *Job) admissionControllers(job *structs.Job) (out *structs.Job, warnings []error, err error) {
// Mutators run first before validators, so validators view the final rendered job.
// So, mutators must handle invalid jobs.
Comment on lines +43 to +44
Copy link
Contributor Author

@notnoop notnoop Mar 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully sure what the motivation of having mutators run first: but this has bitten us before with a similar panic #6575 .

I'm not fully sure of the impact of re-ordering steps, so we validate first then mutate. Skimming code, that would mean that we'd attempt to validating the job first before canonalizing it, which may open another can of worms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really unfortunate in that we really have two types of validation: validation for safety which really wants to happen before mutation (canonicalization, making sure we don't have nil pointers in unexpected places), and validation for "business logic" (ex. task group count for system jobs).

But solving that is beyond the scope of this bug fix, so 👍 for leaving this comment.

out, warnings, err = j.admissionMutators(job)
if err != nil {
return nil, nil, err
Expand Down
51 changes: 51 additions & 0 deletions nomad/job_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,57 @@ func TestJobEndpoint_Register_ConnectWithSidecarTask(t *testing.T) {

}

func TestJobEndpoint_Register_Connect_ValidatesWithoutSidecarTask(t *testing.T) {
t.Parallel()

s1, cleanupS1 := TestServer(t, func(c *Config) {
c.NumSchedulers = 0 // Prevent automatic dequeue
})
defer cleanupS1()
codec := rpcClient(t, s1)
testutil.WaitForLeader(t, s1.RPC)

// Create the register request
job := mock.Job()
job.TaskGroups[0].Networks = structs.Networks{
{
Mode: "bridge",
},
}
job.TaskGroups[0].Tasks[0].Services = nil
job.TaskGroups[0].Services = []*structs.Service{
{
Name: "backend",
PortLabel: "8080",
Connect: &structs.ConsulConnect{
SidecarService: nil,
},
Checks: []*structs.ServiceCheck{{
Name: "exposed_no_sidecar",
Type: "http",
Expose: true,
Path: "/health",
Interval: 10 * time.Second,
Timeout: 2 * time.Second,
}},
},
}

req := &structs.JobRegisterRequest{
Job: job,
WriteRequest: structs.WriteRequest{
Region: "global",
Namespace: job.Namespace,
},
}

// Fetch the response
var resp structs.JobRegisterResponse
err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
require.Error(t, err)
require.Contains(t, err.Error(), "exposed_no_sidecar requires use of sidecar_proxy")
}

// TestJobEndpoint_Register_Connect_AllowUnauthenticatedFalse asserts that a job
// submission fails allow_unauthenticated is false, and either an invalid or no
// operator Consul token is provided.
Expand Down