From ff4dff4be2ae3e771059aa0c74bd1e2a97fd8b5f Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 19 Feb 2024 08:56:49 -0800 Subject: [PATCH 1/3] acl,docs,client: improve documentation on access permissions Updates tailscale/corp#17628 --- acl/acl.go | 27 ++++++++++++++++++++------- client/setec/client.go | 33 ++++++++++++++++++++++++++++----- docs/api.md | 19 +++++++++++++++++++ 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/acl/acl.go b/acl/acl.go index 4e5e8d4..8117184 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -17,16 +17,29 @@ 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. + 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. diff --git a/client/setec/client.go b/client/setec/client.go index 792306d..48dc97e 100644 --- a/client/setec/client.go +++ b/client/setec/client.go @@ -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, @@ -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) @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, diff --git a/docs/api.md b/docs/api.md index 28989b1..97ad311 100644 --- a/docs/api.md +++ b/docs/api.md @@ -12,6 +12,7 @@ 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. @@ -19,6 +20,24 @@ This prevents browser scripts from initiating calls to the service. - 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. | +- `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` From 9b0e109c21ab1b3fd1074f2f8f9b13999b4cef37 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 19 Feb 2024 09:01:26 -0800 Subject: [PATCH 2/3] clarify the relationship between get/info --- acl/acl.go | 2 ++ docs/api.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/acl/acl.go b/acl/acl.go index 8117184..1613b1f 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -18,6 +18,8 @@ type Action string const ( // 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 diff --git a/docs/api.md b/docs/api.md index 97ad311..888313f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -25,7 +25,9 @@ This prevents browser scripts from initiating calls to the service. The service defines named _actions_ that are subject to access control: -- `get`: Denotes permission to fetch the contents of a secret. | +- `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 From 1284a84260daf708e60d56f7f5196e410ecd262a Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 19 Feb 2024 09:11:56 -0800 Subject: [PATCH 3/3] fix punctuation --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 888313f..b3e3eb4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -29,7 +29,7 @@ The service defines named _actions_ that are subject to access control: 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 + available and active version numbers, but not the secret values. - `put`: Denotes permission to put a new value of a secret.