Skip to content

Commit

Permalink
docs,client: improve documentation on access permissions (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
creachadair authored Feb 19, 2024
1 parent a4d9ef5 commit c080348
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 12 deletions.
29 changes: 22 additions & 7 deletions acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,31 @@ import (
type Action string

const (
ActionGet = Action("get")
ActionInfo = Action("info")
ActionPut = Action("put")
// ActionGet ("get" in the API) denotes permission to fetch the contents of a secret.
//
// Note: ActionGet does not imply ActionInfo, or vice versa.
ActionGet = Action("get")

// ActionInfo ("info" in the API) denotes permission to read the metadata
// for a secret, including available and active version numbers, but not the
// secret values.
ActionInfo = Action("info")

// ActionPut ("put" in the API) denotes permission to put a new value of a
// secret.
ActionPut = Action("put")

// ActionActivate ("activate" in the API) denotes permission to set one one
// of of the available versions of a secret as the active one.
ActionActivate = Action("activate")
ActionDelete = Action("delete")

// ActionDelete ("delete" in the API) denotes permission to delete secret
// versions, either individually or entirely.
ActionDelete = Action("delete")
)

// Secret is a secret name pattern that can optionally contain '*'
// wildcard characters. The wildcard means "zero or more of any
// character here."
// Secret is a secret name pattern that can optionally contain '*' wildcard
// characters. The wildcard means "zero or more of any character here."
type Secret string

// Match reports whether the Secret name pattern matches val.
Expand Down
33 changes: 28 additions & 5 deletions client/setec/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,17 @@ func do[RESP, REQ any](ctx context.Context, c Client, path string, req REQ) (RES
return resp, nil
}

// List fetches a list of secret names and associated metadata (but
// not the secret values themselves).
// List fetches a list of secret names and associated metadata for all those
// secrets on which the caller has "info" access. List does not report the
// secret values themselves. If the caller does not have "info" access to any
// secrets, List reports zero values without error.
func (c Client) List(ctx context.Context) ([]*api.SecretInfo, error) {
return do[[]*api.SecretInfo](ctx, c, "/api/list", api.ListRequest{})
}

// Get fetches the current active secret value for name.
//
// Access requirement: "get"
func (c Client) Get(ctx context.Context, name string) (*api.SecretValue, error) {
return do[*api.SecretValue](ctx, c, "/api/get", api.GetRequest{
Name: name,
Expand All @@ -103,6 +107,8 @@ func (c Client) Get(ctx context.Context, name string) (*api.SecretValue, error)
// the same as oldVersion, it reports api.ErrValueNotChanged without returning
// a secret. As a special case, if oldVersion == 0 then GetIfVersion behaves as
// Get and retrieves the current active version.
//
// Access requirement: "get"
func (c Client) GetIfChanged(ctx context.Context, name string, oldVersion api.SecretVersion) (*api.SecretValue, error) {
if oldVersion == api.SecretVersionDefault {
return c.Get(ctx, name)
Expand All @@ -116,6 +122,8 @@ func (c Client) GetIfChanged(ctx context.Context, name string, oldVersion api.Se

// Get fetches a secret value by name and version. If version == 0, GetVersion
// retrieves the current active version.
//
// Access requirement: "get"
func (c Client) GetVersion(ctx context.Context, name string, version api.SecretVersion) (*api.SecretValue, error) {
return do[*api.SecretValue](ctx, c, "/api/get", api.GetRequest{
Name: name,
Expand All @@ -124,15 +132,18 @@ func (c Client) GetVersion(ctx context.Context, name string, version api.SecretV
}

// Info fetches metadata for a given secret name.
//
// Access requirement: "info"
func (c Client) Info(ctx context.Context, name string) (*api.SecretInfo, error) {
return do[*api.SecretInfo](ctx, c, "/api/info", api.InfoRequest{
Name: name,
})
}

// Put creates a secret called name, with the given value. If a secret
// called name already exist, the value is saved as a new inactive
// version.
// Put creates a secret called name, with the given value. If a secret called
// name already exist, the value is saved as a new inactive version.
//
// Access requirement: "put"
func (c Client) Put(ctx context.Context, name string, value []byte) (version api.SecretVersion, err error) {
return do[api.SecretVersion](ctx, c, "/api/put", api.PutRequest{
Name: name,
Expand All @@ -141,6 +152,8 @@ func (c Client) Put(ctx context.Context, name string, value []byte) (version api
}

// Activate changes the active version of the secret called name to version.
//
// Access requirement: "activate"
func (c Client) Activate(ctx context.Context, name string, version api.SecretVersion) error {
_, err := do[struct{}](ctx, c, "/api/activate", api.ActivateRequest{
Name: name,
Expand All @@ -150,6 +163,11 @@ func (c Client) Activate(ctx context.Context, name string, version api.SecretVer
}

// DeleteVersion deletes the specified version of the named secret.
//
// Note: DeleteVersion will report an error if the caller attempts to delete
// the active version, even if they have permission to do so.
//
// Access requirement: "delete"
func (c Client) DeleteVersion(ctx context.Context, name string, version api.SecretVersion) error {
_, err := do[struct{}](ctx, c, "/api/delete-version", api.DeleteVersionRequest{
Name: name,
Expand All @@ -159,6 +177,11 @@ func (c Client) DeleteVersion(ctx context.Context, name string, version api.Secr
}

// Delete deletes all versions of the named secret.
//
// Note: Delete will delete all versions of the secret, including the active
// one, if the caller has permission to do so.
//
// Access requirement: "delete"
func (c Client) Delete(ctx context.Context, name string) error {
_, err := do[struct{}](ctx, c, "/api/delete", api.DeleteRequest{
Name: name,
Expand Down
21 changes: 21 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,34 @@ The peer capability label is `tailscale.com/cap/secrets`.
Calls to the API must include a header `Sec-X-Tailscale-No-Browsers: setec`.
This prevents browser scripts from initiating calls to the service.


## HTTP Status

- Invalid request parameters report 400 Invalid request.
- Access permission errors report 403 Forbidden.
- Requests for unknown values report 404 Not found.
- All other errors report 500 Internal server error.


## Permissions

The service defines named _actions_ that are subject to access control:

- `get`: Denotes permission to fetch the contents of a secret. Note that `get`
does not imply `info`, or vice versa.

- `info`: Denotes permission to read the metadata for a secret, including
available and active version numbers, but not the secret values.

- `put`: Denotes permission to put a new value of a secret.

- `activate`: Denotes permission to set one one of of the available versions of
a secret as the active one.

- `delete`: Denotes permission to delete secret versions, either individually
or entirely.


## Methods

- `/api/list`: List metadata for all secrets to which the caller has `info`
Expand Down

0 comments on commit c080348

Please sign in to comment.