diff --git a/admissionctrl/controller.go b/admissionctrl/controller.go index ac94f0f..d6d67eb 100644 --- a/admissionctrl/controller.go +++ b/admissionctrl/controller.go @@ -6,6 +6,7 @@ package admissionctrl import ( "encoding/json" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" @@ -18,37 +19,39 @@ type AdmissionController interface { type JobMutator interface { AdmissionController - Mutate(*api.Job) (out *api.Job, warnings []error, err error) + Mutate(*types.Payload) (*api.Job, []error, error) } type JobValidator interface { AdmissionController - Validate(*api.Job) (warnings []error, err error) + Validate(*types.Payload) (warnings []error, err error) } type JobHandler struct { - mutators []JobMutator - validators []JobValidator - logger hclog.Logger + mutators []JobMutator + validators []JobValidator + resolveToken bool + logger hclog.Logger } -func NewJobHandler(mutators []JobMutator, validators []JobValidator, logger hclog.Logger) *JobHandler { +func NewJobHandler(mutators []JobMutator, validators []JobValidator, logger hclog.Logger, resolverToken bool) *JobHandler { return &JobHandler{ - mutators: mutators, - validators: validators, - logger: logger, + mutators: mutators, + validators: validators, + logger: logger, + resolveToken: resolverToken, } } -func (j *JobHandler) ApplyAdmissionControllers(job *api.Job) (out *api.Job, warnings []error, err error) { +func (j *JobHandler) ApplyAdmissionControllers(payload *types.Payload) (out *api.Job, warnings []error, err error) { // Mutators run first before validators, so validators view the final rendered job. // So, mutators must handle invalid jobs. - out, warnings, err = j.AdmissionMutators(job) + out, warnings, err = j.AdmissionMutators(payload) if err != nil { return nil, nil, err } - validateWarnings, err := j.AdmissionValidators(job) + validateWarnings, err := j.AdmissionValidators(payload) if err != nil { return nil, nil, err } @@ -57,13 +60,14 @@ func (j *JobHandler) ApplyAdmissionControllers(job *api.Job) (out *api.Job, warn return out, warnings, nil } -// admissionMutator returns an updated job as well as warnings or an error. -func (j *JobHandler) AdmissionMutators(job *api.Job) (_ *api.Job, warnings []error, err error) { +// AdmissionMutators returns an updated job as well as warnings or an error. +func (j *JobHandler) AdmissionMutators(payload *types.Payload) (job *api.Job, warnings []error, err error) { var w []error - j.logger.Debug("applying job mutators", "mutators", len(j.mutators), "job", job.ID) + job = payload.Job + j.logger.Debug("applying job mutators", "mutators", len(j.mutators), "job", payload.Job.ID) for _, mutator := range j.mutators { - j.logger.Debug("applying job mutator", "mutator", mutator.Name(), "job", job.ID) - job, w, err = mutator.Mutate(job) + j.logger.Debug("applying job mutator", "mutator", mutator.Name(), "job", payload.Job.ID) + job, w, err = mutator.Mutate(payload) j.logger.Trace("job mutate results", "mutator", mutator.Name(), "warnings", w, "error", err) if err != nil { return nil, nil, fmt.Errorf("error in job mutator %s: %v", mutator.Name(), err) @@ -75,17 +79,17 @@ func (j *JobHandler) AdmissionMutators(job *api.Job) (_ *api.Job, warnings []err // AdmissionValidators returns a slice of validation warnings and a multierror // of validation failures. -func (j *JobHandler) AdmissionValidators(origJob *api.Job) ([]error, error) { +func (j *JobHandler) AdmissionValidators(payload *types.Payload) ([]error, error) { // ensure job is not mutated - j.logger.Debug("applying job validators", "validators", len(j.validators), "job", origJob.ID) - job := copyJob(origJob) + j.logger.Debug("applying job validators", "validators", len(j.validators), "job", payload.Job.ID) + job := copyJob(payload.Job) var warnings []error var errs error for _, validator := range j.validators { j.logger.Debug("applying job validator", "validator", validator.Name(), "job", job.ID) - w, err := validator.Validate(job) + w, err := validator.Validate(payload) j.logger.Trace("job validate results", "validator", validator.Name(), "warnings", w, "error", err) if err != nil { errs = multierror.Append(errs, err) @@ -97,6 +101,10 @@ func (j *JobHandler) AdmissionValidators(origJob *api.Job) ([]error, error) { } +func (j *JobHandler) ResolveToken() bool { + return j.resolveToken +} + func copyJob(job *api.Job) *api.Job { jobCopy := &api.Job{} data, err := json.Marshal(job) diff --git a/admissionctrl/controller_test.go b/admissionctrl/controller_test.go index 62e481d..84b8d19 100644 --- a/admissionctrl/controller_test.go +++ b/admissionctrl/controller_test.go @@ -1,6 +1,7 @@ package admissionctrl import ( + "github.com/mxab/nacp/admissionctrl/types" "testing" "github.com/hashicorp/go-hclog" @@ -10,7 +11,6 @@ import ( ) func TestJobHandler_ApplyAdmissionControllers(t *testing.T) { - type fields struct { mutator JobMutator validator JobValidator @@ -19,18 +19,21 @@ func TestJobHandler_ApplyAdmissionControllers(t *testing.T) { job *api.Job } job := &api.Job{} // testutil.ReadJob(t) + payload := &types.Payload{Job: job} mutator := new(testutil.MockMutator) - mutator.On("Mutate", job).Return(job, []error{}, nil) + mutator.On("Mutate", payload).Return(payload.Job, []error{}, nil) validator := new(testutil.MockValidator) - validator.On("Validate", job).Return([]error{}, nil) + validator.On("Validate", payload).Return([]error{}, nil) + tests := []struct { - name string - fields fields - args args - want *api.Job - want1 []error - wantErr bool + name string + fields fields + args args + want *api.Job + want1 []error + wantErr bool + resolveToken bool }{ { name: "test", @@ -49,8 +52,9 @@ func TestJobHandler_ApplyAdmissionControllers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - j := NewJobHandler([]JobMutator{tt.fields.mutator}, []JobValidator{tt.fields.validator}, hclog.NewNullLogger()) - _, warnings, err := j.ApplyAdmissionControllers(tt.args.job) + j := NewJobHandler([]JobMutator{tt.fields.mutator}, []JobValidator{tt.fields.validator}, hclog.NewNullLogger(), tt.resolveToken) + payload := &types.Payload{Job: tt.args.job} + _, warnings, err := j.ApplyAdmissionControllers(payload) assert.Empty(t, warnings, "No Warnings") if (err != nil) != tt.wantErr { @@ -66,7 +70,6 @@ func TestJobHandler_ApplyAdmissionControllers(t *testing.T) { mutator.AssertExpectations(t) validator.AssertExpectations(t) - }) } } diff --git a/admissionctrl/mutator/json_patch_webhook.go b/admissionctrl/mutator/json_patch_webhook.go index dfe214a..b9a4ab4 100644 --- a/admissionctrl/mutator/json_patch_webhook.go +++ b/admissionctrl/mutator/json_patch_webhook.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "net/http" "net/url" @@ -36,18 +37,30 @@ func NewJsonPatchWebhookMutator(name string, endpoint string, method string, log method: method, }, nil } -func (j *JsonPatchWebhookMutator) Mutate(job *api.Job) (*api.Job, []error, error) { - - jobJson, err := json.Marshal(job) +func (j *JsonPatchWebhookMutator) Mutate(payload *types.Payload) (*api.Job, []error, error) { + jobJson, err := json.Marshal(payload) if err != nil { return nil, nil, err } - httpClient := &http.Client{} req, err := http.NewRequest(j.method, j.endpoint.String(), bytes.NewBuffer(jobJson)) if err != nil { return nil, nil, err } + + // Add context headers and body if available + if payload.Context != nil { + // Add standard headers for backward compatibility + if payload.Context.ClientIP != "" { + req.Header.Set("X-Forwarded-For", payload.Context.ClientIP) // Standard proxy header + req.Header.Set("NACP-Client-IP", payload.Context.ClientIP) // NACP specific + } + if payload.Context.AccessorID != "" { + req.Header.Set("NACP-Accessor-ID", payload.Context.AccessorID) + } + } + + httpClient := &http.Client{} res, err := httpClient.Do(req) if err != nil { return nil, nil, err @@ -61,7 +74,7 @@ func (j *JsonPatchWebhookMutator) Mutate(job *api.Job) (*api.Job, []error, error var warnings []error if len(patchResponse.Warnings) > 0 { - j.logger.Debug("Got errors from rule", "rule", j.name, "warnings", patchResponse.Warnings, "job", job.ID) + j.logger.Debug("Got errors from rule", "rule", j.name, "warnings", patchResponse.Warnings, "job", payload.Job.ID) for _, warning := range patchResponse.Warnings { warnings = append(warnings, fmt.Errorf(warning)) } @@ -75,7 +88,7 @@ func (j *JsonPatchWebhookMutator) Mutate(job *api.Job) (*api.Job, []error, error if err != nil { return nil, nil, err } - j.logger.Debug("Got patch fom rule", "rule", j.name, "patch", string(patchJson), "job", job.ID) + j.logger.Debug("Got patch fom rule", "rule", j.name, "patch", string(patchJson), "job", payload.Job.ID) patchedJobJson, err := patch.Apply(jobJson) if err != nil { diff --git a/admissionctrl/mutator/json_patch_webhook_test.go b/admissionctrl/mutator/json_patch_webhook_test.go index 188f5f7..08e746e 100644 --- a/admissionctrl/mutator/json_patch_webhook_test.go +++ b/admissionctrl/mutator/json_patch_webhook_test.go @@ -2,6 +2,7 @@ package mutator import ( "fmt" + "github.com/mxab/nacp/admissionctrl/types" "net/http" "net/http/httptest" "testing" @@ -95,7 +96,8 @@ func TestJsonPatchMutator(t *testing.T) { mutator, err := NewJsonPatchWebhookMutator(tc.name, webhookServer.URL+tc.endpointPath, tc.method, hclog.NewNullLogger()) require.NoError(t, err) - job, warnings, err := mutator.Mutate(tc.job) + payload := &types.Payload{Job: tc.job} + job, warnings, err := mutator.Mutate(payload) require.True(t, webhookCalled) assert.Equal(t, tc.wantErr, err) diff --git a/admissionctrl/mutator/opa_json_patch.go b/admissionctrl/mutator/opa_json_patch.go index 955c255..a53621c 100644 --- a/admissionctrl/mutator/opa_json_patch.go +++ b/admissionctrl/mutator/opa_json_patch.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" "fmt" - jsonpatch "github.com/evanphx/json-patch" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/api" "github.com/mxab/nacp/admissionctrl/notation" "github.com/mxab/nacp/admissionctrl/opa" + "github.com/mxab/nacp/admissionctrl/types" ) type OpaJsonPatchMutator struct { @@ -19,11 +19,11 @@ type OpaJsonPatchMutator struct { name string } -func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (*api.Job, []error, error) { +func (j *OpaJsonPatchMutator) Mutate(payload *types.Payload) (*api.Job, []error, error) { allWarnings := make([]error, 0) ctx := context.TODO() - results, err := j.query.Query(ctx, job) + results, err := j.query.Query(ctx, payload) if err != nil { return nil, nil, err } @@ -31,7 +31,7 @@ func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (*api.Job, []error, error) { errors := results.GetErrors() if len(errors) > 0 { - j.logger.Debug("Got errors from rule", "rule", j.Name(), "errors", errors, "job", job.ID) + j.logger.Debug("Got errors from rule", "rule", j.Name(), "errors", errors, "job", payload.Job.ID) allErrors := multierror.Append(nil) for _, warn := range errors { allErrors = multierror.Append(allErrors, fmt.Errorf("%s (%s)", warn, j.Name())) @@ -42,7 +42,7 @@ func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (*api.Job, []error, error) { warnings := results.GetWarnings() if len(warnings) > 0 { - j.logger.Debug("Got warnings from rule", "rule", j.Name(), "warnings", warnings, "job", job.ID) + j.logger.Debug("Got warnings from rule", "rule", j.Name(), "warnings", warnings, "job", payload.Job.ID) for _, warn := range warnings { allWarnings = append(allWarnings, fmt.Errorf("%s (%s)", warn, j.Name())) } @@ -57,8 +57,8 @@ func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (*api.Job, []error, error) { if err != nil { return nil, nil, err } - j.logger.Debug("Got patch fom rule", "rule", j.Name(), "patch", string(patchJSON), "job", job.ID) - jobJson, err := json.Marshal(job) + j.logger.Debug("Got patch fom rule", "rule", j.Name(), "patch", string(patchJSON), "job", payload.Job.ID) + jobJson, err := json.Marshal(payload.Job) if err != nil { return nil, nil, err } @@ -72,9 +72,9 @@ func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (*api.Job, []error, error) { if err != nil { return nil, nil, err } - job = &patchedJob + payload.Job = &patchedJob - return job, allWarnings, nil + return payload.Job, allWarnings, nil } func (j *OpaJsonPatchMutator) Name() string { return j.name diff --git a/admissionctrl/mutator/opa_json_patch_test.go b/admissionctrl/mutator/opa_json_patch_test.go index 9863a0f..e22d733 100644 --- a/admissionctrl/mutator/opa_json_patch_test.go +++ b/admissionctrl/mutator/opa_json_patch_test.go @@ -2,6 +2,7 @@ package mutator import ( "fmt" + "github.com/mxab/nacp/admissionctrl/types" "testing" "github.com/hashicorp/go-hclog" @@ -90,7 +91,8 @@ func TestJSONPatcher_Mutate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotOut, gotWarnings, err := tt.j.Mutate(tt.args.job) + payload := &types.Payload{Job: tt.args.job} + gotOut, gotWarnings, err := tt.j.Mutate(payload) require.Equal(t, tt.wantErr, err != nil, "JSONPatcher.Mutate() error = %v, wantErr %v", err, tt.wantErr) assert.Equal(t, tt.wantWarnings, gotWarnings, "JSONPatcher.Mutate() gotWarnings = %v, want %v", gotWarnings, tt.wantWarnings) diff --git a/admissionctrl/mutator/webhook_mutator.go b/admissionctrl/mutator/webhook_mutator.go index fa2848f..64192f5 100644 --- a/admissionctrl/mutator/webhook_mutator.go +++ b/admissionctrl/mutator/webhook_mutator.go @@ -3,6 +3,8 @@ package mutator import ( "bytes" "encoding/json" + "github.com/mxab/nacp/admissionctrl/types" + "io" "net/http" "net/url" @@ -15,18 +17,30 @@ type WebhookMutator struct { method string } -func (w *WebhookMutator) Mutate(job *api.Job) (out *api.Job, warnings []error, err error) { - - data, err := json.Marshal(job) +func (w *WebhookMutator) Mutate(payload *types.Payload) (out *api.Job, warnings []error, err error) { + data, err := json.Marshal(payload) if err != nil { return nil, nil, err } - buffer := bytes.NewBuffer(data) - - req, err := http.NewRequest(w.method, w.endpoint.String(), buffer) + req, err := http.NewRequest(w.method, w.endpoint.String(), bytes.NewBuffer(data)) if err != nil { return nil, nil, err } + + // Add context headers and body if available + if payload.Context != nil { + // Add standard headers for backward compatibility + if payload.Context.ClientIP != "" { + req.Header.Set("X-Forwarded-For", payload.Context.ClientIP) // Standard proxy header + req.Header.Set("NACP-Client-IP", payload.Context.ClientIP) // NACP specific + } + if payload.Context.AccessorID != "" { + req.Header.Set("NACP-Accessor-ID", payload.Context.AccessorID) + } + } + + req.Body = io.NopCloser(bytes.NewBuffer(data)) + req.ContentLength = int64(len(data)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} diff --git a/admissionctrl/mutator/webhook_mutator_test.go b/admissionctrl/mutator/webhook_mutator_test.go index 1de4ef6..43af6e7 100644 --- a/admissionctrl/mutator/webhook_mutator_test.go +++ b/admissionctrl/mutator/webhook_mutator_test.go @@ -3,6 +3,7 @@ package mutator import ( "encoding/json" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "net/http" "net/http/httptest" "net/url" @@ -74,7 +75,8 @@ func TestWebhookMutator_Mutate(t *testing.T) { endpoint: endpoint, method: tt.fields.method, } - gotOut, gotWarnings, err := w.Mutate(tt.args.job) + payload := &types.Payload{Job: tt.args.job} + gotOut, gotWarnings, err := w.Mutate(payload) assert.True(t, endpointCalled, "Ensure endpoint was called") if (err != nil) != tt.wantErr { t.Errorf("WebhookMutator.Mutate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/admissionctrl/opa/opa.go b/admissionctrl/opa/opa.go index fe963d2..3921840 100644 --- a/admissionctrl/opa/opa.go +++ b/admissionctrl/opa/opa.go @@ -3,9 +3,9 @@ package opa import ( "context" "errors" + types2 "github.com/mxab/nacp/admissionctrl/types" "os" - "github.com/hashicorp/nomad/api" "github.com/mxab/nacp/admissionctrl/notation" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" @@ -60,8 +60,8 @@ func CreateQuery(filename string, query string, ctx context.Context, verifier no }, nil } -func (q *OpaQuery) Query(ctx context.Context, input *api.Job) (*OpaQueryResult, error) { - resultSet, err := q.query.Eval(ctx, rego.EvalInput(input)) +func (q *OpaQuery) Query(ctx context.Context, payload *types2.Payload) (*OpaQueryResult, error) { + resultSet, err := q.query.Eval(ctx, rego.EvalInput(payload)) if err != nil { return nil, err } diff --git a/admissionctrl/opa/opa_test.go b/admissionctrl/opa/opa_test.go index 24727f3..b143d25 100644 --- a/admissionctrl/opa/opa_test.go +++ b/admissionctrl/opa/opa_test.go @@ -3,6 +3,7 @@ package opa import ( "context" "errors" + "github.com/mxab/nacp/admissionctrl/types" "testing" "github.com/hashicorp/nomad/api" @@ -26,7 +27,8 @@ func TestOpa(t *testing.T) { assert.NotNil(t, query, "Query is not nil") job := &api.Job{} - result, err := query.Query(ctx, job) + payload := &types.Payload{Job: job} + result, err := query.Query(ctx, payload) assert.Nil(t, err, "No error executing query") assert.NotNil(t, result, "Result is not nil") @@ -58,7 +60,8 @@ func TestFailOnEmptyResultSet(t *testing.T) { assert.NotNil(t, query, "Query is not nil") job := &api.Job{} - result, err := query.Query(ctx, job) + payload := &types.Payload{Job: job} + result, err := query.Query(ctx, payload) assert.Error(t, err, "Error executing query") assert.Nil(t, result, "Result is nil") @@ -75,7 +78,8 @@ func TestReturnsEmptyIfNotExisting(t *testing.T) { require.Nil(t, err, "No error creating query") assert.NotNil(t, query, "Query is not nil") job := &api.Job{} - result, err := query.Query(ctx, job) + payload := &types.Payload{Job: job} + result, err := query.Query(ctx, payload) assert.Nil(t, err, "No error executing query") assert.NotNil(t, result, "Result is not nil") @@ -153,7 +157,8 @@ func TestNotationImageValidation(t *testing.T) { }, } require.NoError(t, err, "No error creating query") - result, err := query.Query(ctx, job) + payload := &types.Payload{Job: job} + result, err := query.Query(ctx, payload) require.NoError(t, err, "No error executing query") require.NotNil(t, result, "Result is not nil") diff --git a/admissionctrl/types/opa_payload.go b/admissionctrl/types/opa_payload.go new file mode 100644 index 0000000..882f6d1 --- /dev/null +++ b/admissionctrl/types/opa_payload.go @@ -0,0 +1,11 @@ +package types + +import ( + "github.com/hashicorp/nomad/api" + "github.com/mxab/nacp/config" +) + +type Payload struct { + Job *api.Job `json:"job"` + Context *config.RequestContext `json:"context,omitempty"` +} diff --git a/admissionctrl/validator/notation_validator.go b/admissionctrl/validator/notation_validator.go index 04dd27f..3066bdd 100644 --- a/admissionctrl/validator/notation_validator.go +++ b/admissionctrl/validator/notation_validator.go @@ -2,9 +2,9 @@ package validator import ( "context" + "github.com/mxab/nacp/admissionctrl/types" "github.com/hashicorp/go-hclog" - "github.com/hashicorp/nomad/api" "github.com/mxab/nacp/admissionctrl/notation" ) @@ -14,8 +14,8 @@ type NotationValidator struct { verifier notation.ImageVerifier } -func (v *NotationValidator) Validate(job *api.Job) ([]error, error) { - for _, tg := range job.TaskGroups { +func (v *NotationValidator) Validate(payload *types.Payload) ([]error, error) { + for _, tg := range payload.Job.TaskGroups { for _, task := range tg.Tasks { // check if the task driver is docker // should we consider podman? diff --git a/admissionctrl/validator/notation_validator_test.go b/admissionctrl/validator/notation_validator_test.go index 1544d48..d1de07a 100644 --- a/admissionctrl/validator/notation_validator_test.go +++ b/admissionctrl/validator/notation_validator_test.go @@ -3,6 +3,7 @@ package validator import ( "context" "errors" + "github.com/mxab/nacp/admissionctrl/types" "testing" "github.com/hashicorp/go-hclog" @@ -122,9 +123,13 @@ func TestNotationValidatorValidate(t *testing.T) { }) } - errors, err := notationValidator.Validate(&api.Job{ - TaskGroups: groups, - }) + payload := &types.Payload{ + Job: &api.Job{ + TaskGroups: groups, + }, + } + + errors, err := notationValidator.Validate(payload) require.Equal(t, tc.expectedErrors, errors) require.NoError(t, err) diff --git a/admissionctrl/validator/opa_validator.go b/admissionctrl/validator/opa_validator.go index 1bfb7f5..e38469a 100644 --- a/admissionctrl/validator/opa_validator.go +++ b/admissionctrl/validator/opa_validator.go @@ -3,12 +3,11 @@ package validator import ( "context" "fmt" - "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/nomad/api" "github.com/mxab/nacp/admissionctrl/notation" "github.com/mxab/nacp/admissionctrl/opa" + "github.com/mxab/nacp/admissionctrl/types" ) type OpaValidator struct { @@ -17,18 +16,17 @@ type OpaValidator struct { name string } -func (v *OpaValidator) Validate(job *api.Job) ([]error, error) { +func (v *OpaValidator) Validate(payload *types.Payload) ([]error, error) { ctx := context.TODO() //iterate over rulesets and evaluate allErrs := &multierror.Error{} allWarnings := make([]error, 0) - v.logger.Debug("Validating job", "job", job.ID) + v.logger.Debug("Validating job", "job", payload.Job.ID) // evaluate the query - - results, err := v.query.Query(ctx, job) + results, err := v.query.Query(ctx, payload) if err != nil { return nil, err @@ -38,7 +36,7 @@ func (v *OpaValidator) Validate(job *api.Job) ([]error, error) { warnings := results.GetWarnings() if len(warnings) > 0 { - v.logger.Debug("Got warnings from rule", "rule", v.Name(), "warnings", warnings, "job", job.ID) + v.logger.Debug("Got warnings from rule", "rule", v.Name(), "warnings", warnings, "job", payload.Job.ID) for _, warn := range warnings { allWarnings = append(allWarnings, fmt.Errorf("%s (%s)", warn, v.Name())) } @@ -47,7 +45,7 @@ func (v *OpaValidator) Validate(job *api.Job) ([]error, error) { errors := results.GetErrors() if len(errors) > 0 { // no errors is ok - v.logger.Debug("Got errors from rule", "rule", v.Name(), "errors", errors, "job", job.ID) + v.logger.Debug("Got errors from rule", "rule", v.Name(), "errors", errors, "job", payload.Job.ID) errsForRule := &multierror.Error{} for _, err := range errors { errsForRule = multierror.Append(errsForRule, fmt.Errorf("%s (%s)", err, v.Name())) diff --git a/admissionctrl/validator/opa_validator_test.go b/admissionctrl/validator/opa_validator_test.go index aa14eea..115d1e0 100644 --- a/admissionctrl/validator/opa_validator_test.go +++ b/admissionctrl/validator/opa_validator_test.go @@ -1,6 +1,7 @@ package validator import ( + "github.com/mxab/nacp/admissionctrl/types" "testing" "github.com/hashicorp/go-hclog" @@ -14,7 +15,7 @@ func TestOpaValidator(t *testing.T) { // create a context with a timeout // create a new OPA object - opa, err := NewOpaValidator("testopavalidator", testutil.Filepath(t, "opa/validators/prefixed_policies.rego"), + opaValidator, err := NewOpaValidator("testopavalidator", testutil.Filepath(t, "opa/validators/prefixed_policies.rego"), "errors = data.prefixed_policies.errors", hclog.NewNullLogger(), nil) require.Equal(t, nil, err) @@ -38,7 +39,8 @@ func TestOpaValidator(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { job := testutil.ReadJob(t, tt.jobFile) - _, err := opa.Validate(job) + payload := &types.Payload{Job: job} + _, err := opaValidator.Validate(payload) require.Equal(t, tt.wantErr, err != nil, "OpaValidator.Validate() error = %v, wantErr %v", err, tt.wantErr) }) @@ -90,10 +92,11 @@ func TestOpaValidatorSimple(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - opa, err := NewOpaValidator("testopavalidator", testutil.Filepath(t, "opa/errors.rego"), + opaValidator, err := NewOpaValidator("testopavalidator", testutil.Filepath(t, "opa/errors.rego"), tt.query, hclog.NewNullLogger(), nil) require.NoError(t, err) - warnings, err := opa.Validate(dummyJob) + payload := &types.Payload{Job: dummyJob} + warnings, err := opaValidator.Validate(payload) require.Equal(t, tt.wantErr, err != nil, "OpaValidator.Validate() error = %v, wantErr %v", err, tt.wantErr) assert.Len(t, warnings, tt.wantWarnings, "OpaValidator.Validate() warnings = %v, wantWarnings %v", warnings, tt.wantWarnings) }) diff --git a/admissionctrl/validator/webhook_validator.go b/admissionctrl/validator/webhook_validator.go index 2397a1b..42e646d 100644 --- a/admissionctrl/validator/webhook_validator.go +++ b/admissionctrl/validator/webhook_validator.go @@ -4,12 +4,12 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "net/http" "net/url" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/nomad/api" ) type WebhookValidator struct { @@ -24,9 +24,8 @@ type validationWebhookResponse struct { Warnings []string `json:"warnings"` } -func (w *WebhookValidator) Validate(job *api.Job) ([]error, error) { - - data, err := json.Marshal(job) +func (w *WebhookValidator) Validate(payload *types.Payload) ([]error, error) { + data, err := json.Marshal(payload) if err != nil { return nil, err } @@ -35,6 +34,18 @@ func (w *WebhookValidator) Validate(job *api.Job) ([]error, error) { return nil, err } + // Add context headers and body if available + if payload.Context != nil { + // Add standard headers for backward compatibility + if payload.Context.ClientIP != "" { + req.Header.Set("X-Forwarded-For", payload.Context.ClientIP) // Standard proxy header + req.Header.Set("NACP-Client-IP", payload.Context.ClientIP) // NACP specific + } + if payload.Context.AccessorID != "" { + req.Header.Set("NACP-Accessor-ID", payload.Context.AccessorID) + } + } + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err @@ -48,7 +59,7 @@ func (w *WebhookValidator) Validate(job *api.Job) ([]error, error) { } if len(valdationResult.Errors) > 0 { - w.logger.Error("validation errors", "errors", valdationResult.Errors, "rule", w.name, "job", job.ID) + w.logger.Error("validation errors", "errors", valdationResult.Errors, "rule", w.name, "job", payload.Job.ID) oneError := &multierror.Error{} for _, e := range valdationResult.Errors { oneError = multierror.Append(oneError, fmt.Errorf("%v", e)) diff --git a/admissionctrl/validator/webhook_validator_test.go b/admissionctrl/validator/webhook_validator_test.go index 070bf4e..746dbdb 100644 --- a/admissionctrl/validator/webhook_validator_test.go +++ b/admissionctrl/validator/webhook_validator_test.go @@ -3,6 +3,7 @@ package validator import ( "encoding/json" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "io" "net/http" "net/http/httptest" @@ -18,11 +19,6 @@ import ( // WebhookValidator is a validator that uses a webhook to validate a job. func TestWebhookValidator(t *testing.T) { - //calls and endpoint with a job, returns a json response with result.errors and result.warnings fields - //if result.errors is not empty, return an error - //if result.warnings is not empty, return a all warnings - - //Setup Table test, with different responses tt := []struct { name string endpointPath string @@ -77,31 +73,31 @@ func TestWebhookValidator(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { webhookCalled = true - expectedJobJsonData, err := json.Marshal(&api.Job{ID: &tc.name}) + var payload types.Payload + jsonData, err := io.ReadAll(r.Body) + require.NoError(t, err) + err = json.Unmarshal(jsonData, &payload) require.NoError(t, err) + expectedJob := &api.Job{ID: &tc.name} + assert.Equal(t, expectedJob.ID, payload.Job.ID) assert.Equal(t, tc.endpointPath, r.URL.Path) assert.Equal(t, tc.method, r.Method) - data, err := io.ReadAll(r.Body) - require.NoError(t, err) - assert.JSONEq(t, string(expectedJobJsonData), string(data)) w.WriteHeader(http.StatusOK) w.Write([]byte(tc.response)) - })) defer server.Close() - //Test validator, err := NewWebhookValidator("test", server.URL+tc.endpointPath, tc.method, hclog.NewNullLogger()) require.NoError(t, err) - warnings, err := validator.Validate(&api.Job{ID: &tc.name}) + payload := &types.Payload{Job: &api.Job{ID: &tc.name}} + warnings, err := validator.Validate(payload) require.True(t, webhookCalled, "webhook was not called") assert.Equal(t, tc.wantErr, err) assert.Equal(t, tc.wantWarnings, warnings) }) } - } diff --git a/cmd/nacp/nacp.go b/cmd/nacp/nacp.go index 96b80fc..2401356 100644 --- a/cmd/nacp/nacp.go +++ b/cmd/nacp/nacp.go @@ -9,6 +9,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/mxab/nacp/admissionctrl/types" "io" "net" "net/http" @@ -17,6 +18,7 @@ import ( "os" "regexp" "strconv" + "strings" "time" "github.com/hashicorp/go-hclog" @@ -44,6 +46,58 @@ var ( nomadTimeout = 310 * time.Second ) +// New function to get client IP +func getClientIP(r *http.Request) string { + // Check X-Forwarded-For header first + forwarded := r.Header.Get("X-Forwarded-For") + if forwarded != "" { + return strings.Split(forwarded, ",")[0] + } + + // Fall back to RemoteAddr + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + return ip +} + +func resolveTokenAccessor(transport http.RoundTripper, nomadAddress *url.URL, token string) (*api.ACLToken, error) { + if token == "" { + return nil, nil + } + + client := &http.Client{ + Transport: transport, + } + if transport == nil { + client = http.DefaultClient + } + + selfURL := *nomadAddress + selfURL.Path = "/v1/acl/token/self" + + req, err := http.NewRequest("GET", selfURL.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("X-Nomad-Token", token) + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to resolve token: %s", resp.Status) + } + + var aclToken api.ACLToken + if err := json.NewDecoder(resp.Body).Decode(&aclToken); err != nil { + return nil, err + } + + return &aclToken, nil +} func NewProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler, appLogger hclog.Logger, transport *http.Transport) func(http.ResponseWriter, *http.Request) { proxy := httputil.NewSingleHostReverseProxy(nomadAddress) @@ -80,8 +134,29 @@ func NewProxyHandler(nomadAddress *url.URL, jobHandler *admissionctrl.JobHandler appLogger.Info("Request received", "path", r.URL.Path, "method", r.Method) + ctx := r.Context() + reqCtx := &config.RequestContext{ + ClientIP: getClientIP(r), + } + + token := r.Header.Get("X-Nomad-Token") + if jobHandler.ResolveToken() { + tokenInfo, err := resolveTokenAccessor(transport, nomadAddress, token) + if err != nil { + appLogger.Error("Resolving token failed", "error", err) + writeError(w, err) + } + if tokenInfo != nil { + reqCtx.AccessorID = tokenInfo.AccessorID + reqCtx.TokenInfo = tokenInfo + } + } + + // Store context + ctx = context.WithValue(ctx, "request_context", reqCtx) + r = r.WithContext(ctx) + var err error - //var err error if isRegister(r) { r, err = handleRegister(r, appLogger, jobHandler) @@ -286,8 +361,15 @@ func handleRegister(r *http.Request, appLogger hclog.Logger, jobHandler *admissi return r, fmt.Errorf("failed decoding job, skipping admission controller: %w", err) } orginalJob := jobRegisterRequest.Job + payload := &types.Payload{ + Job: orginalJob, + } + + if reqCtx, ok := r.Context().Value("request_context").(*config.RequestContext); ok { + payload.Context = reqCtx + } - job, warnings, err := jobHandler.ApplyAdmissionControllers(orginalJob) + job, warnings, err := jobHandler.ApplyAdmissionControllers(payload) if err != nil { return r, fmt.Errorf("admission controllers send an error, returning error: %w", err) } @@ -317,8 +399,15 @@ func handlePlan(r *http.Request, appLogger hclog.Logger, jobHandler *admissionct return r, fmt.Errorf("failed decoding job, skipping admission controller: %w", err) } orginalJob := jobPlanRequest.Job + payload := &types.Payload{ + Job: orginalJob, + } + + if reqCtx, ok := r.Context().Value("request_context").(*config.RequestContext); ok { + payload.Context = reqCtx + } - job, warnings, err := jobHandler.ApplyAdmissionControllers(orginalJob) + job, warnings, err := jobHandler.ApplyAdmissionControllers(payload) if err != nil { return r, fmt.Errorf("admission controllers send an error, returning error: %w", err) } @@ -350,15 +439,22 @@ func handleValidate(r *http.Request, appLogger hclog.Logger, jobHandler *admissi return r, err } job := jobValidateRequest.Job + payload := &types.Payload{ + Job: job, + } - job, mutateWarnings, err := jobHandler.AdmissionMutators(job) + if reqCtx, ok := r.Context().Value("request_context").(*config.RequestContext); ok { + payload.Context = reqCtx + } + job, mutateWarnings, err := jobHandler.AdmissionMutators(payload) if err != nil { return r, err } jobValidateRequest.Job = job + payload.Job = job - validateWarnings, err := jobHandler.AdmissionValidators(job) + validateWarnings, err := jobHandler.AdmissionValidators(payload) //copied from https: //github.com/hashicorp/nomad/blob/v1.5.0/nomad/job_endpoint.go#L574 ctx := r.Context() @@ -457,20 +553,28 @@ func buildServer(c *config.Config, appLogger hclog.Logger) (*http.Server, error) } proxyTransport.TLSClientConfig = nomadTlsConfig } - jobMutators, err := createMutators(c, appLogger.Named("mutators")) + + jobMutators, resolveTokenMutators, err := createMutators(c, appLogger.Named("mutators")) if err != nil { return nil, fmt.Errorf("failed to create mutators: %w", err) } - jobValidators, err := createValidators(c, appLogger.Named("validators")) + + jobValidators, resolveTokenValidators, err := createValidators(c, appLogger.Named("validators")) if err != nil { return nil, fmt.Errorf("failed to create validators: %w", err) } + var resolveToken bool + if resolveTokenMutators || resolveTokenValidators { + resolveToken = true + } + handler := admissionctrl.NewJobHandler( jobMutators, jobValidators, appLogger.Named("handler"), + resolveToken, ) proxy := NewProxyHandler(backend, handler, appLogger, proxyTransport) @@ -535,72 +639,79 @@ func createTlsConfig(caFile string, noClientCert bool) (*tls.Config, error) { return tlsConfig, nil } -func createMutators(c *config.Config, logger hclog.Logger) ([]admissionctrl.JobMutator, error) { +func createMutators(c *config.Config, logger hclog.Logger) ([]admissionctrl.JobMutator, bool, error) { var jobMutators []admissionctrl.JobMutator + var resolveToken bool for _, m := range c.Mutators { + if m.ResolveToken { + resolveToken = true + } switch m.Type { - case "opa_json_patch": notationVerifier, err := buildVerifierIfEnabled(m.OpaRule.Notation, logger.Named("notation_verifier")) if err != nil { - return nil, err + return nil, resolveToken, err } mutator, err := mutator.NewOpaJsonPatchMutator(m.Name, m.OpaRule.Filename, m.OpaRule.Query, logger.Named("opa_mutator"), notationVerifier) if err != nil { - return nil, err + return nil, resolveToken, err } jobMutators = append(jobMutators, mutator) case "json_patch_webhook": mutator, err := mutator.NewJsonPatchWebhookMutator(m.Name, m.Webhook.Endpoint, m.Webhook.Method, logger.Named("json_patch_webhook_mutator")) if err != nil { - return nil, err + return nil, resolveToken, err } jobMutators = append(jobMutators, mutator) default: - return nil, fmt.Errorf("unknown mutator type %s", m.Type) + return nil, resolveToken, fmt.Errorf("unknown mutator type %s", m.Type) } } - return jobMutators, nil + return jobMutators, resolveToken, nil } -func createValidators(c *config.Config, logger hclog.Logger) ([]admissionctrl.JobValidator, error) { +func createValidators(c *config.Config, logger hclog.Logger) ([]admissionctrl.JobValidator, bool, error) { var jobValidators []admissionctrl.JobValidator + var resolveToken bool for _, v := range c.Validators { + if v.ResolveToken { + resolveToken = true + } switch v.Type { case "opa": notationVerifier, err := buildVerifierIfEnabled(v.Notation, logger.Named("notation_verifier")) if err != nil { - return nil, err + return nil, resolveToken, err } opaValidator, err := validator.NewOpaValidator(v.Name, v.OpaRule.Filename, v.OpaRule.Query, logger.Named("opa_validator"), notationVerifier) if err != nil { - return nil, err + return nil, resolveToken, err } jobValidators = append(jobValidators, opaValidator) case "webhook": validator, err := validator.NewWebhookValidator(v.Name, v.Webhook.Endpoint, v.Webhook.Method, logger.Named("webhook_validator")) if err != nil { - return nil, err + return nil, resolveToken, err } jobValidators = append(jobValidators, validator) case "notation": notationVerifier, err := buildVerifier(v.Notation, logger.Named("notation_verifier")) if err != nil { - return nil, err + return nil, resolveToken, err } validator := validator.NewNotationValidator(logger.Named("notation_validator"), v.Name, notationVerifier) jobValidators = append(jobValidators, validator) default: - return nil, fmt.Errorf("unknown validator type %s", v.Type) + return nil, resolveToken, fmt.Errorf("unknown validator type %s", v.Type) } } - return jobValidators, nil + return jobValidators, resolveToken, nil } func buildVerifierIfEnabled(notationVerifierConfig *config.NotationVerifierConfig, logger hclog.Logger) (notation.ImageVerifier, error) { if notationVerifierConfig == nil { diff --git a/cmd/nacp/nacp_test.go b/cmd/nacp/nacp_test.go index fb390dc..e5f1578 100644 --- a/cmd/nacp/nacp_test.go +++ b/cmd/nacp/nacp_test.go @@ -40,6 +40,10 @@ func TestProxy(t *testing.T) { path string method string + token string + resolveToken bool + accessorID string + requestSender func(*api.Client) (interface{}, *api.WriteMeta, error) wantNomadRequestJson string wantProxyResponse interface{} @@ -305,6 +309,31 @@ func TestProxy(t *testing.T) { Warnings: helper.MergeMultierrorWarnings(errors.New("some warning")), }, + nomadResponse: toJson(t, &api.JobValidateResponse{}), + nomadResponseEncoding: "gzip", + validators: []admissionctrl.JobValidator{ + mockValidatorReturningWarnings("some warning"), + }, + mutators: []admissionctrl.JobMutator{}, + }, + { + name: "resolves token during job creation", + path: "/v1/validate/job", + + method: "PUT", + token: "test-token", + resolveToken: true, + accessorID: "test-accessor", + + requestSender: func(c *api.Client) (interface{}, *api.WriteMeta, error) { + return c.Jobs().Validate(&api.Job{}, nil) + }, + wantNomadRequestJson: toJson(t, &api.JobValidateRequest{Job: &api.Job{}}), + + wantProxyResponse: &api.JobValidateResponse{ + Warnings: helper.MergeMultierrorWarnings(errors.New("some warning")), + }, + nomadResponse: toJson(t, &api.JobValidateResponse{}), nomadResponseEncoding: "gzip", validators: []admissionctrl.JobValidator{ @@ -317,64 +346,75 @@ func TestProxy(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { nomadBackendCalled := false + tokenCalled := false nomadDummy := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - // Test request parameters nomadBackendCalled = true - assert.Equal(t, req.Method, tc.method, "Ensure method is set") - assert.Equal(t, req.URL.Path, tc.path, "Ensure path is set") - jsonData, err := io.ReadAll(req.Body) - if err != nil { - t.Fatal(err) + if req.URL.Path == "/v1/acl/token/self" { + tokenCalled = true + if tc.token != "test-token" { + rw.WriteHeader(http.StatusUnauthorized) + return + } + json.NewEncoder(rw).Encode(&api.ACLToken{ + AccessorID: tc.accessorID, + Name: "test-token", + Policies: []string{"test-policy"}, + Type: "client", + Global: false, + }) + return } - json := string(jsonData) - assert.JSONEq(t, tc.wantNomadRequestJson, json, "Body matches") - //set encoding to gzip + assert.Equal(t, req.Method, tc.method) + assert.Equal(t, req.URL.Path, tc.path) + jsonData, err := io.ReadAll(req.Body) + require.NoError(t, err) + assert.JSONEq(t, tc.wantNomadRequestJson, string(jsonData)) + if tc.nomadResponseEncoding == "gzip" { rw.Header().Set("Content-Encoding", "gzip") rw.WriteHeader(http.StatusOK) - //write gzip response gzipWriter := gzip.NewWriter(rw) defer gzipWriter.Close() gzipWriter.Write([]byte(tc.nomadResponse)) - } else { rw.WriteHeader(http.StatusOK) rw.Write([]byte(tc.nomadResponse)) } - })) - // Close the server when test finishes defer nomadDummy.Close() - // Use Client & URL from our local test server + nomadURL, err := url.Parse(nomadDummy.URL) + require.NoError(t, err) - nomad, err := url.Parse(nomadDummy.URL) - if err != nil { - t.Fatal(err) - } + proxyTransport := http.DefaultTransport.(*http.Transport).Clone() jobHandler := admissionctrl.NewJobHandler( tc.mutators, tc.validators, hclog.NewNullLogger(), + tc.resolveToken, ) - proxy := NewProxyHandler(nomad, jobHandler, hclog.NewNullLogger(), nil) + proxy := NewProxyHandler(nomadURL, jobHandler, hclog.NewNullLogger(), proxyTransport) proxyServer := httptest.NewServer(http.HandlerFunc(proxy)) defer proxyServer.Close() nomadClient := buildNomadClient(t, proxyServer) + if tc.token != "" { + nomadClient.SetSecretID(tc.token) + } resp, _, err := tc.requestSender(nomadClient) + if tc.resolveToken { + assert.True(t, tokenCalled, "token resolution should be called") + } - assert.NoError(t, err, "No http call error") - assert.Equal(t, tc.wantProxyResponse, resp, "OK response is expected") - + require.NoError(t, err, "No http call error") + assert.Equal(t, tc.wantProxyResponse, resp, "Response matches") assert.True(t, nomadBackendCalled, "Nomad backend was called") - }) } - } + func TestJobUpdateProxy(t *testing.T) { type test struct { @@ -440,6 +480,7 @@ func TestJobUpdateProxy(t *testing.T) { tc.mutators, tc.validators, hclog.NewNullLogger(), + false, ) proxy := NewProxyHandler(nomad, jobHandler, hclog.NewNullLogger(), nil) @@ -540,6 +581,7 @@ func TestAdmissionControllerErrors(t *testing.T) { []admissionctrl.JobMutator{}, []admissionctrl.JobValidator{validator}, hclog.NewNullLogger(), + false, ) proxy := NewProxyHandler(nomad, jobHandler, hclog.NewNullLogger(), nil) @@ -657,7 +699,7 @@ func TestCreateValidators(t *testing.T) { Validators: []config.Validator{tc.validators}, } - validators, err := createValidators(c, hclog.NewNullLogger()) + validators, _, err := createValidators(c, hclog.NewNullLogger()) if tc.wantErr { assert.Error(t, err) @@ -715,7 +757,7 @@ func TestNotationValidatorConfig(t *testing.T) { }, } - validators, err := createValidators(c, hclog.NewNullLogger()) + validators, _, err := createValidators(c, hclog.NewNullLogger()) assert.NoError(t, err) assert.IsType(t, &validator.NotationValidator{}, validators[0]) @@ -776,7 +818,7 @@ func TestCreateMutatators(t *testing.T) { Mutators: []config.Mutator{tc.mutators}, } - mutators, err := createMutators(c, hclog.NewNullLogger()) + mutators, _, err := createMutators(c, hclog.NewNullLogger()) if tc.wantErr { assert.Error(t, err) diff --git a/config/config.go b/config/config.go index 7969a92..b8aae3c 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsimple" + "github.com/hashicorp/nomad/api" ) type Webhook struct { @@ -16,18 +17,27 @@ type OpaRule struct { } type Validator struct { - Type string `hcl:"type,label"` - Name string `hcl:"name,label"` - OpaRule *OpaRule `hcl:"opa_rule,block"` - Webhook *Webhook `hcl:"webhook,block"` + Type string `hcl:"type,label"` + Name string `hcl:"name,label"` + OpaRule *OpaRule `hcl:"opa_rule,block"` + Webhook *Webhook `hcl:"webhook,block"` + ResolveToken bool `hcl:"resolve_token,optional"` Notation *NotationVerifierConfig `hcl:"notation,block"` } type Mutator struct { - Type string `hcl:"type,label"` - Name string `hcl:"name,label"` - OpaRule *OpaRule `hcl:"opa_rule,block"` - Webhook *Webhook `hcl:"webhook,block"` + Type string `hcl:"type,label"` + Name string `hcl:"name,label"` + OpaRule *OpaRule `hcl:"opa_rule,block"` + Webhook *Webhook `hcl:"webhook,block"` + ResolveToken bool `hcl:"resolve_token,optional"` +} + +type RequestContext struct { + ClientIP string `json:"clientIP"` + AccessorID string `json:"accessorID"` + ResolveToken bool `json:"resolveToken"` + TokenInfo *api.ACLToken `json:"tokenInfo,omitempty"` } type NomadServerTLS struct { diff --git a/testdata/opa/test_notation.rego b/testdata/opa/test_notation.rego index 2417080..cd8ce74 100644 --- a/testdata/opa/test_notation.rego +++ b/testdata/opa/test_notation.rego @@ -2,7 +2,7 @@ package opatest errors[errMsg] { - image := input.TaskGroups[0].Tasks[0].Config.image + image := input.job.TaskGroups[0].Tasks[0].Config.image not notation_verify_image(image) errMsg := "Image is not in valid" diff --git a/testdata/opa/validators/costcenter_meta.rego b/testdata/opa/validators/costcenter_meta.rego index fa6dc1b..afa7023 100644 --- a/testdata/opa/validators/costcenter_meta.rego +++ b/testdata/opa/validators/costcenter_meta.rego @@ -12,7 +12,7 @@ errors contains msg if { # of the keys are missing, this statement will be true. - not input.Meta.costcenter + not input.job.Meta.costcenter trace("Costcenter code is missing") msg := "Every job must have a costcenter metadata label" @@ -21,7 +21,7 @@ errors contains msg if { # This definition checks if the costcenter label is formatted appropriately. Each rule # definition contributes to the set of error messages. errors contains msg if { - value := input.Meta.costcenter + value := input.job.Meta.costcenter not startswith(value, "cccode-") msg := sprintf("Costcenter code must start with `cccode-`; found `%v`", [value]) diff --git a/testdata/opa/validators/prefixed_policies.rego b/testdata/opa/validators/prefixed_policies.rego index e2aacd7..0c61555 100644 --- a/testdata/opa/validators/prefixed_policies.rego +++ b/testdata/opa/validators/prefixed_policies.rego @@ -4,22 +4,22 @@ import future.keywords import future.keywords.in task_group_policies contains name if { - name := input.TaskGroups[_].Vault.Policies[_] + name := input.job.TaskGroups[_].Vault.Policies[_] } task_policies contains name if { - name := input.TaskGroups[_].Tasks[_].Vault.Policies[_] + name := input.job.TaskGroups[_].Tasks[_].Vault.Policies[_] } -policy_prefix := sprintf("%s-", [input.ID]) +policy_prefix := sprintf("%s-", [input.job.ID]) errors[msg] { - + some p in task_policies not startswith(p, policy_prefix) msg := sprintf("Task policy '%v' must start with '%v'", [p, policy_prefix]) } errors[msg] { - + some p in task_group_policies not startswith(p, policy_prefix) msg := sprintf("Task group policy '%v' must start with '%v'", [p, policy_prefix]) diff --git a/testutil/hello_mutator.go b/testutil/hello_mutator.go index dbe6942..478e941 100644 --- a/testutil/hello_mutator.go +++ b/testutil/hello_mutator.go @@ -1,20 +1,23 @@ package testutil -import "github.com/hashicorp/nomad/api" +import ( + "github.com/hashicorp/nomad/api" + "github.com/mxab/nacp/admissionctrl/types" +) type HelloMutator struct { MutatorName string } -func (h *HelloMutator) Mutate(job *api.Job) (out *api.Job, warnings []error, err error) { +func (h *HelloMutator) Mutate(payload *types.Payload) (out *api.Job, warnings []error, err error) { - if job.Meta == nil { - job.Meta = make(map[string]string) + if payload.Job.Meta == nil { + payload.Job.Meta = make(map[string]string) } - job.Meta["hello"] = "world" + payload.Job.Meta["hello"] = "world" - return job, nil, nil + return payload.Job, nil, nil } func (h *HelloMutator) Name() string { diff --git a/testutil/testutil.go b/testutil/testutil.go index 9bd5f0a..321d9c6 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -2,6 +2,7 @@ package testutil import ( "encoding/json" + "github.com/mxab/nacp/admissionctrl/types" "io" "os" "path" @@ -54,8 +55,8 @@ type MockMutator struct { mock.Mock } -func (m *MockMutator) Mutate(job *api.Job) (out *api.Job, warnings []error, err error) { - args := m.Called(job) +func (m *MockMutator) Mutate(payload *types.Payload) (out *api.Job, warnings []error, err error) { + args := m.Called(payload) return args.Get(0).(*api.Job), args.Get(1).([]error), args.Error(2) } func (m *MockMutator) Name() string { @@ -66,8 +67,8 @@ type MockValidator struct { mock.Mock } -func (m *MockValidator) Validate(job *api.Job) (warnings []error, err error) { - args := m.Called(job) +func (m *MockValidator) Validate(payload *types.Payload) (warnings []error, err error) { + args := m.Called(payload) return args.Get(0).([]error), args.Error(1) } func (m *MockValidator) Name() string {