Skip to content

Commit

Permalink
Add support for ephemeral keys (#51)
Browse files Browse the repository at this point in the history
This commit adds a new option when reading tailnet keys that allows for the creation of
ephemeral keys. It also standardises the error handling across the backend and adds
additional documentation for how to customise key options when reading.

Closes #50

Signed-off-by: David Bond <davidsbond93@gmail.com>
  • Loading branch information
davidsbond authored Mar 7, 2023
1 parent 5253df3 commit 4a6b677
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 12 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ $ vault write tailscale/config tailnet=$TAILNET api_key=$API_KEY
Success! Data written to: tailscale/config
```

2. Generate keys using the Vault CLI.
3. Generate keys using the Vault CLI.

```shell
$ vault read tailscale/key
Expand All @@ -54,3 +54,31 @@ key secret-key-data
reusable false
tags <nil>
```

### Key Options

The following key/value pairs can be added to the end of the `vault read` command to configure key properties:

#### Tags

Tags to apply to the device that uses the authentication key

```
vault read tailscale/key tags=something:somewhere
```

#### Preauthorized

If true, machines added to the tailnet with this key will not required authorization

```
vault read tailscale/key preauthorized=true
```

#### Ephemeral

If true, nodes created with this key will be removed after a period of inactivity or when they disconnect from the Tailnet

```
vault read tailscale/key ephemeral=true
```
21 changes: 16 additions & 5 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package backend

import (
"context"
"errors"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
Expand Down Expand Up @@ -35,6 +36,7 @@ const (
tagsDescription = "Tags to apply to the device that uses the authentication key"
preauthorizedDescription = "If true, machines added to the tailnet with this key will not required authorization"
apiUrlDescription = "The URL of the Tailscale API"
ephemeralDescription = "If true, nodes created with this key will be removed after a period of inactivity or when they disconnect from the Tailnet"
)

// Create a new logical.Backend implementation that can generate authentication keys for Tailscale devices.
Expand All @@ -55,6 +57,10 @@ func Create(ctx context.Context, config *logical.BackendConfig) (logical.Backend
Type: framework.TypeBool,
Description: preauthorizedDescription,
},
"ephemeral": {
Type: framework.TypeBool,
Description: ephemeralDescription,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Expand Down Expand Up @@ -122,6 +128,7 @@ func (b *Backend) GenerateKey(ctx context.Context, request *logical.Request, dat
var capabilities tailscale.KeyCapabilities
capabilities.Devices.Create.Tags = data.Get("tags").([]string)
capabilities.Devices.Create.Preauthorized = data.Get("preauthorized").(bool)
capabilities.Devices.Create.Ephemeral = data.Get("ephemeral").(bool)

key, err := client.CreateKey(ctx, capabilities)
if err != nil {
Expand All @@ -148,7 +155,7 @@ func (b *Backend) ReadConfiguration(ctx context.Context, request *logical.Reques
case err != nil:
return nil, err
case entry == nil:
return logical.ErrorResponse("configuration has not been set"), nil
return nil, errors.New("configuration has not been set")
}

var config Config
Expand All @@ -175,17 +182,21 @@ func (b *Backend) UpdateConfiguration(ctx context.Context, request *logical.Requ

switch {
case config.Tailnet == "":
return logical.ErrorResponse("provided tailnet cannot be empty"), nil
return nil, errors.New("provided tailnet cannot be empty")
case config.APIKey == "":
return logical.ErrorResponse("provided api_key cannot be empty"), nil
return nil, errors.New("provided api_key cannot be empty")
case config.APIUrl == "":
return logical.ErrorResponse("provided api_url cannot be empty"), nil
return nil, errors.New("provided api_url cannot be empty")
}

entry, err := logical.StorageEntryJSON(configPath, config)
if err != nil {
return nil, err
}

return nil, request.Storage.Put(ctx, entry)
if err = request.Storage.Put(ctx, entry); err != nil {
return nil, err
}

return &logical.Response{}, nil
}
16 changes: 10 additions & 6 deletions backend/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ func TestBackend_GenerateKey(t *testing.T) {
"preauthorized": {
Type: framework.TypeBool,
},
"ephemeral": {
Type: framework.TypeBool,
},
}

tt := []struct {
Expand Down Expand Up @@ -75,10 +78,11 @@ func TestBackend_GenerateKey(t *testing.T) {
response, err := b.GenerateKey(ctx, tc.Request, tc.Data)

if tc.ExpectsError {
assert.Error(t, response.Error())
assert.Error(t, err)
return
}

assert.NoError(t, err)
assert.EqualValues(t, tc.Expected, response.Data)
})
}
Expand Down Expand Up @@ -125,13 +129,13 @@ func TestBackend_ReadConfiguration(t *testing.T) {
}

response, err := b.ReadConfiguration(ctx, tc.Request, tc.Data)
assert.NoError(t, err)

if tc.ExpectsError {
assert.Error(t, response.Error())
assert.Error(t, err)
return
}

assert.NoError(t, err)
assert.EqualValues(t, tc.Expected, response.Data)
})
}
Expand Down Expand Up @@ -202,14 +206,14 @@ func TestBackend_UpdateConfiguration(t *testing.T) {

for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
response, err := b.UpdateConfiguration(ctx, tc.Request, tc.Data)
assert.NoError(t, err)
_, err := b.UpdateConfiguration(ctx, tc.Request, tc.Data)

if tc.ExpectsError {
assert.Error(t, response.Error())
assert.Error(t, err)
return
}

assert.NoError(t, err)
assert.EqualValues(t, tc.Expected, getConfig(t, ctx, tc.Request))
})
}
Expand Down

0 comments on commit 4a6b677

Please sign in to comment.