From 02379d494bb01a9dba6c8cab739e48198b3121bb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 17:24:05 -0700 Subject: [PATCH] Add support for extensions and critical options on the identity function. --- authority/provisioner/oidc.go | 8 ++++ authority/provisioner/provisioner.go | 9 +++- sshutil/templates.go | 21 ++++++++- sshutil/templates_test.go | 69 ++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index bb70444c..870541cd 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -384,6 +384,14 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption if v, err := unsafeParseSigned(token); err == nil { data.SetToken(v) } + // Add custom extensions added in the identity function. + for k, v := range iden.Permissions.Extensions { + data.AddExtension(k, v) + } + // Add custom critical options added in the identity function. + for k, v := range iden.Permissions.CriticalOptions { + data.AddCriticalOption(k, v) + } templateOptions, err := TemplateSSHOptions(o.SSHOptions, data) if err != nil { diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index c413a100..aed1900a 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -326,7 +326,14 @@ func (b *base) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certif // Identity is the type representing an externally supplied identity that is used // by provisioners to populate certificate fields. type Identity struct { - Usernames []string `json:"usernames"` + Usernames []string `json:"usernames"` + Permissions `json:"permissions"` +} + +// Permissions defines extra extensions and critical options to grant to an SSH certificate. +type Permissions struct { + Extensions map[string]string `json:"extensions"` + CriticalOptions map[string]string `json:"criticalOptions"` } // GetIdentityFunc is a function that returns an identity. diff --git a/sshutil/templates.go b/sshutil/templates.go index 8710f8e2..f51b46f6 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -5,6 +5,7 @@ const ( KeyIDKey = "KeyID" PrincipalsKey = "Principals" ExtensionsKey = "Extensions" + CriticalOptionsKey = "CriticalOptions" TokenKey = "Token" InsecureKey = "Insecure" UserKey = "User" @@ -70,6 +71,17 @@ func (t TemplateData) AddExtension(key, value string) { } } +// AddCriticalOption adds one critical option to the templates data. +func (t TemplateData) AddCriticalOption(key, value string) { + if m, ok := t[CriticalOptionsKey].(map[string]interface{}); ok { + m[key] = value + } else { + t[CriticalOptionsKey] = map[string]interface{}{ + key: value, + } + } +} + // Set sets a key-value pair in the template data. func (t TemplateData) Set(key string, v interface{}) { t[key] = v @@ -104,6 +116,12 @@ func (t TemplateData) SetExtensions(e map[string]interface{}) { t.Set(ExtensionsKey, e) } +// SetCriticalOptions sets the certificate critical options in the template +// data. +func (t TemplateData) SetCriticalOptions(o map[string]interface{}) { + t.Set(CriticalOptionsKey, o) +} + // SetToken sets the given token in the template data. func (t TemplateData) SetToken(v interface{}) { t.Set(TokenKey, v) @@ -126,7 +144,8 @@ const DefaultCertificate = `{ "type": "{{ .Type }}", "keyId": "{{ .KeyID }}", "principals": {{ toJson .Principals }}, - "extensions": {{ toJson .Extensions }} + "extensions": {{ toJson .Extensions }}, + "criticalOptions": {{ toJson .CriticalOptions }} }` const DefaultIIDCertificate = `{ diff --git a/sshutil/templates_test.go b/sshutil/templates_test.go index 0d411c32..ac4152b5 100644 --- a/sshutil/templates_test.go +++ b/sshutil/templates_test.go @@ -157,6 +157,46 @@ func TestTemplateData_AddExtension(t *testing.T) { } } +func TestTemplateData_AddCriticalOption(t *testing.T) { + type args struct { + key string + value string + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"empty", TemplateData{}, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }}, + {"overwrite", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{ + "key": "value", + }, + }}, + {"add", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{ + "key": "value", + "foo": "bar", + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.AddCriticalOption(tt.args.key, tt.args.value) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("AddCriticalOption() = %v, want %v", tt.t, tt.want) + } + }) + } +} + func TestTemplateData_Set(t *testing.T) { type args struct { key string @@ -325,6 +365,35 @@ func TestTemplateData_SetExtensions(t *testing.T) { } } +func TestTemplateData_SetCriticalOptions(t *testing.T) { + type args struct { + e map[string]interface{} + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }}, + {"overwrite", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }, args{map[string]interface{}{"key": "value"}}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetCriticalOptions(tt.args.e) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetCriticalOptions() = %v, want %v", tt.t, tt.want) + } + }) + } +} + func TestTemplateData_SetToken(t *testing.T) { type args struct { v interface{}