From 3e80f41c19a6b7bc10907fae8f114051d12c1a14 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 17:44:22 -0700 Subject: [PATCH] Change provisioner options to have X509 as a field. --- authority/provisioner/oidc.go | 2 +- authority/provisioner/options.go | 36 ++++++++++++++------- authority/provisioner/options_test.go | 46 ++++++++++++++++++++------- authority/tls_test.go | 6 ++-- 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index c4fa4839..8cbe849d 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -325,7 +325,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // Use the default template unless no-templates are configured and email is // an admin, in that case we will use the CR template. defaultTemplate := x509util.DefaultLeafTemplate - if !o.Options.HasTemplate() && o.IsAdmin(claims.Email) { + if !o.Options.GetX509Options().HasTemplate() && o.IsAdmin(claims.Email) { defaultTemplate = x509util.CertificateRequestTemplate } diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index ba1c8efb..c56b6064 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -24,6 +24,19 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option { // Options are a collection of custom options that can be added to // each provisioner. type Options struct { + X509 *X509Options `json:"x509,omitempty"` +} + +// GetX509Options returns the X.509Options +func (o *Options) GetX509Options() *X509Options { + if o == nil { + return nil + } + return o.X509 +} + +// X509Options contains specific options for X.509 certificates. +type X509Options struct { // Template contains a X.509 certificate template. It can be a JSON template // escaped in a string or it can be also encoded in base64. Template string `json:"template"` @@ -37,7 +50,7 @@ type Options struct { } // HasTemplate returns true if a template is defined in the provisioner options. -func (o *Options) HasTemplate() bool { +func (o *X509Options) HasTemplate() bool { return o != nil && (o.Template != "" || o.TemplateFile != "") } @@ -54,14 +67,15 @@ func TemplateOptions(o *Options, data x509util.TemplateData) (CertificateOptions // user data provided in the request. If no template has been provided in the // ProvisionerOptions, the given template will be used. func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTemplate string) (CertificateOptions, error) { - if o != nil { - if data == nil { - data = x509util.NewTemplateData() - } + opts := o.GetX509Options() + if data == nil { + data = x509util.NewTemplateData() + } + if opts != nil { // Add template data if any. - if len(o.TemplateData) > 0 { - if err := json.Unmarshal(o.TemplateData, &data); err != nil { + if len(opts.TemplateData) > 0 { + if err := json.Unmarshal(opts.TemplateData, &data); err != nil { return nil, errors.Wrap(err, "error unmarshaling template data") } } @@ -69,7 +83,7 @@ func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTempla return certificateOptionsFunc(func(so SignOptions) []x509util.Option { // We're not provided user data without custom templates. - if !o.HasTemplate() { + if !opts.HasTemplate() { return []x509util.Option{ x509util.WithTemplate(defaultTemplate, data), } @@ -86,15 +100,15 @@ func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTempla } // Load a template from a file if Template is not defined. - if o.Template == "" && o.TemplateFile != "" { + if opts.Template == "" && opts.TemplateFile != "" { return []x509util.Option{ - x509util.WithTemplateFile(o.TemplateFile, data), + x509util.WithTemplateFile(opts.TemplateFile, data), } } // Load a template from the Template fields // 1. As a JSON in a string. - template := strings.TrimSpace(o.Template) + template := strings.TrimSpace(opts.Template) if strings.HasPrefix(template, "{") { return []x509util.Option{ x509util.WithTemplate(template, data), diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index a7ab3ece..54b07bf3 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -24,7 +24,29 @@ func parseCertificateRequest(t *testing.T, filename string) *x509.CertificateReq return csr } -func TestProvisionerOptions_HasTemplate(t *testing.T) { +func TestOptions_GetX509Options(t *testing.T) { + type fields struct { + o *Options + } + tests := []struct { + name string + fields fields + want *X509Options + }{ + {"ok", fields{&Options{X509: &X509Options{Template: "foo"}}}, &X509Options{Template: "foo"}}, + {"nil", fields{&Options{}}, nil}, + {"nilOptions", fields{nil}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.o.GetX509Options(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Options.GetX509Options() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvisionerX509Options_HasTemplate(t *testing.T) { type fields struct { Template string TemplateFile string @@ -42,7 +64,7 @@ func TestProvisionerOptions_HasTemplate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - o := &Options{ + o := &X509Options{ Template: tt.fields.Template, TemplateFile: tt.fields.TemplateFile, TemplateData: tt.fields.TemplateData, @@ -81,14 +103,14 @@ func TestTemplateOptions(t *testing.T) { "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, - {"okCustomTemplate", args{&Options{Template: x509util.DefaultIIDLeafTemplate}, data}, x509util.Options{ + {"okCustomTemplate", args{&Options{X509: &X509Options{Template: x509util.DefaultIIDLeafTemplate}}, data}, x509util.Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, - {"fail", args{&Options{TemplateData: []byte(`{"badJSON`)}, data}, x509util.Options{}, true}, + {"fail", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON`)}}, data}, x509util.Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -157,27 +179,27 @@ func TestCustomTemplateOptions(t *testing.T) { "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, - {"okTemplateData", args{&Options{TemplateData: []byte(`{"foo":"bar"}`)}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + {"okTemplateData", args{&Options{X509: &X509Options{TemplateData: []byte(`{"foo":"bar"}`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foobar"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, - {"okTemplate", args{&Options{Template: "{{ toJson .Insecure.CR }}"}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + {"okTemplate", args{&Options{X509: &X509Options{Template: "{{ toJson .Insecure.CR }}"}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, - {"okFile", args{&Options{TemplateFile: "./testdata/templates/cr.tpl"}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + {"okFile", args{&Options{X509: &X509Options{TemplateFile: "./testdata/templates/cr.tpl"}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, - {"okBase64", args{&Options{Template: "e3sgdG9Kc29uIC5JbnNlY3VyZS5DUiB9fQ=="}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + {"okBase64", args{&Options{X509: &X509Options{Template: "e3sgdG9Kc29uIC5JbnNlY3VyZS5DUiB9fQ=="}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, - {"okUserOptions", args{&Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"foo":"bar"}`)}}, x509util.Options{ + {"okUserOptions", args{&Options{X509: &X509Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"foo":"bar"}`)}}, x509util.Options{ CertBuffer: bytes.NewBufferString(`{"foo": "bar"}`), }, false}, - {"okBadUserOptions", args{&Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"badJSON"}`)}}, x509util.Options{ + {"okBadUserOptions", args{&Options{X509: &X509Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"badJSON"}`)}}, x509util.Options{ CertBuffer: bytes.NewBufferString(`{"foo": ""}`), }, false}, - {"fail", args{&Options{TemplateData: []byte(`{"badJSON`)}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, - {"failTemplateData", args{&Options{TemplateData: []byte(`{"badJSON}`)}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, + {"fail", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, + {"failTemplateData", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON}`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/tls_test.go b/authority/tls_test.go index 7e6ffc9b..90dc7075 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -288,7 +288,7 @@ ZYtQ9Ot36qc= t.Fatal("provisioner not found") } p.(*provisioner.JWK).Options = &provisioner.Options{ - Template: `{{ fail "fail message" }}`, + X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`}, } testExtraOpts, err := testAuthority.Authorize(ctx, token) assert.FatalError(t, err) @@ -366,12 +366,12 @@ ZYtQ9Ot36qc= t.Fatal("provisioner not found") } p.(*provisioner.JWK).Options = &provisioner.Options{ - Template: `{ + X509: &provisioner.X509Options{Template: `{ "subject": {{toJson .Subject}}, "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth","clientAuth"] - }`, + }`}, } testExtraOpts, err := testAuthority.Authorize(ctx, token) assert.FatalError(t, err)