Skip to content
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
115 changes: 102 additions & 13 deletions keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,71 @@ type createOAuthClientWithKeyTypeRequest struct {
CreateOAuthClientRequest
}

// SetOAuthClientRequest describes the definition of an existing OAuth client to
// set (wholesale update) the configuration of.
type SetOAuthClientRequest struct {
Scopes []string `json:"scopes"`
Tags []string `json:"tags"`
Description string `json:"description"`
}

type setOAuthClientWithKeyTypeRequest struct {
KeyType string `json:"keyType"`
SetOAuthClientRequest
}

// CreateFederatedIdentityRequest describes the definition of a federated identity to create.
type CreateFederatedIdentityRequest struct {
Scopes []string `json:"scopes"`
Tags []string `json:"tags"`
Audience string `json:"audience"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
CustomClaimRules map[string]string `json:"customClaimRules"`
Description string `json:"description"`
}

type createFederatedIdentityWithKeyTypeRequest struct {
KeyType string `json:"keyType"`
CreateFederatedIdentityRequest
}

// SetFederatedIdentityRequest describes the definition of a federated identity to create.
type SetFederatedIdentityRequest struct {
Scopes []string `json:"scopes"`
Tags []string `json:"tags"`
Audience string `json:"audience"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
CustomClaimRules map[string]string `json:"customClaimRules"`
Description string `json:"description"`
}

type setFederatedIdentityWithKeyTypeRequest struct {
KeyType string `json:"keyType"`
SetFederatedIdentityRequest
}

// Key describes an authentication key within the tailnet.
type Key struct {
ID string `json:"id"`
KeyType string `json:"keyType"`
Key string `json:"key"`
Description string `json:"description"`
ExpirySeconds *time.Duration `json:"expirySeconds"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
Revoked time.Time `json:"revoked"`
Invalid bool `json:"invalid"`
Capabilities KeyCapabilities `json:"capabilities"`
Scopes []string `json:"scopes,omitempty"`
Tags []string `json:"tags,omitempty"`
UserID string `json:"userId"`
ID string `json:"id"`
KeyType string `json:"keyType"`
Key string `json:"key"`
Description string `json:"description"`
ExpirySeconds *time.Duration `json:"expirySeconds"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Expires time.Time `json:"expires"`
Revoked time.Time `json:"revoked"`
Invalid bool `json:"invalid"`
Capabilities KeyCapabilities `json:"capabilities"`
Scopes []string `json:"scopes,omitempty"`
Tags []string `json:"tags,omitempty"`
UserID string `json:"userId"`
Audience string `json:"audience"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
CustomClaimRules map[string]string `json:"customClaimRules"`
}

// Create creates a new authentication key. Returns the generated [Key] if successful.
Expand Down Expand Up @@ -91,6 +141,45 @@ func (kr *KeysResource) CreateOAuthClient(ctx context.Context, ckr CreateOAuthCl
return body[Key](kr, req)
}

// SetOAuthClient sets the configuration for an existing OAuth client. Returns the generated [Key] if successful.
func (kr *KeysResource) SetOAuthClient(ctx context.Context, id string, skr SetOAuthClientRequest) (*Key, error) {
req, err := kr.buildRequest(ctx, http.MethodPut, kr.buildTailnetURL("keys", id), requestBody(setOAuthClientWithKeyTypeRequest{
KeyType: "client",
SetOAuthClientRequest: skr,
}))
if err != nil {
return nil, err
}

return body[Key](kr, req)
}

// CreateFederatedIdentity creates a new federated identity. Returns the generated [Key] if successful.
func (kr *KeysResource) CreateFederatedIdentity(ctx context.Context, ckr CreateFederatedIdentityRequest) (*Key, error) {
req, err := kr.buildRequest(ctx, http.MethodPost, kr.buildTailnetURL("keys"), requestBody(createFederatedIdentityWithKeyTypeRequest{
KeyType: "federated",
CreateFederatedIdentityRequest: ckr,
}))
if err != nil {
return nil, err
}

return body[Key](kr, req)
}

// SetFederatedIdentity sets the configuration for an existing federated identity. Returns the generated [Key] if successful.
func (kr *KeysResource) SetFederatedIdentity(ctx context.Context, id string, skr SetFederatedIdentityRequest) (*Key, error) {
req, err := kr.buildRequest(ctx, http.MethodPut, kr.buildTailnetURL("keys", id), requestBody(setFederatedIdentityWithKeyTypeRequest{
KeyType: "federated",
SetFederatedIdentityRequest: skr,
}))
if err != nil {
return nil, err
}

return body[Key](kr, req)
}

// Get returns all information on a [Key] whose identifier matches the one provided. This will not return the
// authentication key itself, just the metadata.
func (kr *KeysResource) Get(ctx context.Context, id string) (*Key, error) {
Expand Down
135 changes: 135 additions & 0 deletions keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,141 @@ func TestClient_CreateOAuthClient(t *testing.T) {
assert.EqualValues(t, "", actualReq.Description)
}

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

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

expected := &Key{
ID: "test",
KeyType: "client",
Key: "thisisatestclient",
ExpirySeconds: nil,
Created: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Expires: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
Description: "",
}

server.ResponseBody = expected

actual, err := client.Keys().SetOAuthClient(context.Background(), "test", SetOAuthClientRequest{
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
})
assert.NoError(t, err)
assert.EqualValues(t, expected, actual)
assert.Equal(t, http.MethodPut, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/keys/test", server.Path)

var actualReq createOAuthClientWithKeyTypeRequest
assert.NoError(t, json.Unmarshal(server.Body.Bytes(), &actualReq))
assert.EqualValues(t, "client", actualReq.KeyType)
assert.EqualValues(t, 1, len(actualReq.Scopes))
assert.EqualValues(t, "all:read", actualReq.Scopes[0])
assert.EqualValues(t, 1, len(actualReq.Tags))
assert.EqualValues(t, "tag:test", actualReq.Tags[0])
assert.EqualValues(t, "", actualReq.Description)
}

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

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

expected := &Key{
ID: "test",
KeyType: "federated",
Key: "thisisatestclient",
ExpirySeconds: nil,
Created: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Expires: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
Description: "",
Audience: "exampleaud",
Subject: "examplesubject",
CustomClaimRules: map[string]string{
"foo": "bar",
},
}

server.ResponseBody = expected

actual, err := client.Keys().CreateFederatedIdentity(context.Background(), CreateFederatedIdentityRequest{
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
Subject: "examplesubject",
CustomClaimRules: map[string]string{
"foo": "bar",
},
})
assert.NoError(t, err)
assert.EqualValues(t, expected, actual)
assert.Equal(t, http.MethodPost, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/keys", server.Path)

var actualReq createFederatedIdentityWithKeyTypeRequest
assert.NoError(t, json.Unmarshal(server.Body.Bytes(), &actualReq))
assert.EqualValues(t, "federated", actualReq.KeyType)
assert.EqualValues(t, 1, len(actualReq.Scopes))
assert.EqualValues(t, "all:read", actualReq.Scopes[0])
assert.EqualValues(t, 1, len(actualReq.Tags))
assert.EqualValues(t, "tag:test", actualReq.Tags[0])
assert.EqualValues(t, "", actualReq.Description)
}

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

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

expected := &Key{
ID: "test",
KeyType: "federated",
Key: "thisisatestclient",
ExpirySeconds: nil,
Created: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Expires: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
Description: "",
Audience: "exampleaud",
Subject: "examplesubject",
CustomClaimRules: map[string]string{
"foo": "bar",
},
}

server.ResponseBody = expected

actual, err := client.Keys().SetFederatedIdentity(context.Background(), "test", SetFederatedIdentityRequest{
Scopes: []string{"all:read"},
Tags: []string{"tag:test"},
Subject: "examplesubject",
CustomClaimRules: map[string]string{
"foo": "bar",
},
})
assert.NoError(t, err)
assert.EqualValues(t, expected, actual)
assert.Equal(t, http.MethodPut, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/keys/test", server.Path)

var actualReq createFederatedIdentityWithKeyTypeRequest
assert.NoError(t, json.Unmarshal(server.Body.Bytes(), &actualReq))
assert.EqualValues(t, "federated", actualReq.KeyType)
assert.EqualValues(t, 1, len(actualReq.Scopes))
assert.EqualValues(t, "all:read", actualReq.Scopes[0])
assert.EqualValues(t, 1, len(actualReq.Tags))
assert.EqualValues(t, "tag:test", actualReq.Tags[0])
assert.EqualValues(t, "", actualReq.Description)
}

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

Expand Down