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

Implement AccessorID resolution and client ip propagation #19

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
50 changes: 29 additions & 21 deletions admissionctrl/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
27 changes: 15 additions & 12 deletions admissionctrl/controller_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package admissionctrl

import (
"github.com/mxab/nacp/admissionctrl/types"
"testing"

"github.com/hashicorp/go-hclog"
Expand All @@ -10,7 +11,6 @@ import (
)

func TestJobHandler_ApplyAdmissionControllers(t *testing.T) {

type fields struct {
mutator JobMutator
validator JobValidator
Expand All @@ -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",
Expand All @@ -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 {
Expand All @@ -66,7 +70,6 @@ func TestJobHandler_ApplyAdmissionControllers(t *testing.T) {

mutator.AssertExpectations(t)
validator.AssertExpectations(t)

})
}
}
25 changes: 19 additions & 6 deletions admissionctrl/mutator/json_patch_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/mxab/nacp/admissionctrl/types"
"net/http"
"net/url"

Expand Down Expand Up @@ -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
Expand All @@ -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))
}
Expand All @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion admissionctrl/mutator/json_patch_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mutator

import (
"fmt"
"github.com/mxab/nacp/admissionctrl/types"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 9 additions & 9 deletions admissionctrl/mutator/opa_json_patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -19,19 +19,19 @@ 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
}

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()))
Expand All @@ -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()))
}
Expand All @@ -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
}
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion admissionctrl/mutator/opa_json_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mutator

import (
"fmt"
"github.com/mxab/nacp/admissionctrl/types"
"testing"

"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 20 additions & 6 deletions admissionctrl/mutator/webhook_mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package mutator
import (
"bytes"
"encoding/json"
"github.com/mxab/nacp/admissionctrl/types"
"io"
"net/http"
"net/url"

Expand All @@ -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{}
Expand Down
Loading
Loading