Integrate with Vault to retrieve and manage per session credentials (#1308)

* Fix down migrations

* Udpates due to merge

* Sentinel values only have to be greater than 0

* Create credential related sdk structs and methods.

* Post merge interface updating.

* Removing scratch code that was not cleaned up.

* updates

* Convert client certificate and client certificate keys into pem blocks and validate.

* Add newline at end of file.

* Faster check for "s" at the end of a resource name when generating from templates.

* make fmt

* updates

* Add dynamic credentials to a session

* Refactor: move contents of file

* updates

* Updates

* Add InvalidDynamicCredential error

* Assign credentials

* Allow updating client certificates seperate from the client certificate key.

* Replace application purpose string with enum value

* PR feedback / fix go test .

* Delete dead code

* Use common view

* Refactor: extract interface and rename method

* Refactor: move code to eliminate privPurpLibrary

* Add comment to requestMap

* Refactor: do not export methods on an unexported struct

* Update internal/db/schema/migrations/postgres/10/03_vault_credential.up.sql

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>

* Add comments to credential Purpose constants

* Reorder switch statements to handle errors first

* PR feedback

* update

* fix merge

* Organizing the scheduler code and extract the individual store creation test helper function.

* Run 'make fmt'

* Create certificate when creating store.  Allow vault address to be passed in as option

* Refactor: rename database views and associated structs

This change renames two database views and associated go structs to use
a consistent naming pattern. Views ending in 'private' contain encrypted
credentials needed for connecting to Vault. Views ending in 'public'
mirror the 'private' views but do not include any encrypted values.

This change does not include any changes to functionality or behavior.

* Adding SAD target operations for credential libraries to the SDK.

* Add status field and enumeration table for credential status

* Refactor: rename Status to TokenStatus

* Add status to vault credentials

* Creating the template for the CLI and helper functions.

* Add private database view for vault credentials

* Add scope_id to view and cleanup TODO comment

* Reformat embedded sql queries

* Add vault client helpers

* updates

* white space

* Add LookupLease test

* CLI commands for Store work.  Renamed fields in proto w/ vault prefix.

* Test issue credentials with client TLS

* Add additional checks to vault ping

* Add an error for vault token capabilities and create a VaultToken error kind

* Add support for working with Vault policies

* Use Vault 1.7.2 for testing

* Add testing helper for creating a vault client with a non-root token

* Fix Vault renew lease test

The Vault renew lease endpoint accepts an optional duration parameter
for requesting the number of seconds to extend the lease by. However,
Vault is not required to honor this request.

* Add a var for the required capabilities of a boundary Vault token

* Enhance the test helper for adding a policy to Vault

* Allow the test role in postgresql to revoke credentials

* Remove out of date comments

* Enhance Vault test helpers for the database secrets engine

* Reformat comments

* Add a method for getting the capabilities of the current Vault token

* Add revoke lease to internal Vault client

* Reformat: combine err return line with check for nil line

* Remove redundant checks in test code

Remove checks in test code that are redundant with checks in the test
harness.

* Refactor CreateToken test helper to return the secret and the token

* Fix build failures caused by signature change in vault.CreateToken

* Fix Vault version and Vault API link in comments

* ran `make gen` after merging from mgaffney-vault.

* Remove Vault prefix from attribute fields in API, create a base CLI option for adding field prefixes. format now uses that prefix for error reporting.

* Template now allows attaching attribute field prefixes.

* Credential store uses PrefixAttributeFieldErrorsWithSubactionPrefix.

* Fixing naming of vault flags and helper flags.

* Updating more references to generated api methods.

* Make the capabilities String method more readable

* Add comment outlining PrintApiError usage of Options.

* Refactor test to move declarations closer to first use

* Fix comment

* Implement revoke method on vault repository

* Tests now verify certs, CA, and tokens, addresses can be updated in a store.

* Added test for creating store with pk/cert in same field.

* Allo updating certificates seperate from private key.  Only contact backing vault service when updating token.

* Fixing bad tests and removing unused code.

* Adding tests for various ways of updating tokens.

* Add private credential

* Add token revocation job and cred renewal/revocation job

* Regen after merge.

* Fixing field mask related merge error.

* Create Library CLI. Removes vault prefix from path. Adds a WithoutCleanup testOption for vault.

* Set all credentials to revoked when a token is revoked

* Fixing missed renaming update.

* fix copy/paste error.

* Fixing code generation errors after merge.

* make gen after merge.

* Update internal/cmd/commands/credentialstorescmd/vault_funcs.go

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>

* Update internal/cmd/commands/credentialstorescmd/vault_funcs.go

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>

* Update internal/cmd/commands/credentialstorescmd/vault_funcs.go

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>

* Refactor: move migrations from 10 to 12

* Removing logic from transaction.

* wrapping client errors in the calling op for CredentialStore's client() method.

* Redact token

* redact client key

* updates

* Add trigger to revoke credentials when session is canceled or terminated

* Fixing some help text for some flags.

* Add ability to load values from files (file://) or environment variables (env://).

* Updating comments

* fix bad merge

* feedback

* Adding issued credentials to sessions.

* Error on capabilities

* updates

* Expose NewVaulTestServer

* Add godoc

* Adding generated SessionCredential struct for API.

* Progress on building handler test for Authorize Session.

* feedback

* Update policies

* Add 'revoke' as a status for Vault Tokens

* Add delete_time to vault credential store

* Create dynamic credentials at session creation time.

* Tests pass with credentials attached to AuthorizedSession result.

* Only call issue if there are credentials to issue.

* Fixing output only issues. Adding type to credential library.

* Fix test to clear un/pw generated.  confirm generated un/pw changes from 1 authorize session call to another.

* Update lookupTokenError

* Add delete_time to private credential store

* Refactor: move private store to separate file

* Fixing tests for AuthorizeSession and verifying error cases.

* Only create the credential repo if there are credentials to issue.

* Rename Library field to CredentialLibrary.

* Fixing missing change of Library to CredentialLibrary.

* Removing Default SDK functions for fields which are required and update CLI to match.

* Replace resource specific id variables with "id" and the input parmater for paths to be just a single string and the resource name as it should show up in the path.

* Soft delete vault credential stores

* Prevent new SQL functions from silently overwriting existing functions

* Rename "Hmac" to "HMAC" in the human readable cli response table.

* Fix tests

* Add vault database cleanup jobs

* Add ability to set the token period in the vault test harness

* updates

* Add not_null_columns func test

* remove unused struct

* Up no output timeout

* Validate current vault token on addr update

* Fix client cert change test

* add cert test

* Update revocation job where

* Fix comment

* Use Vault's default port in CLI help strings

* Remove down migrations

* Increase migration file numbers by 1

* Run make migrations

* Move migrations from 12 to 10

* Increase Circle CI timeout for acceptance tests

* Fix spelling mistake in error messages

* ASD cmds for Target and returning credentials to AuthSession request (#1338)

* Add Add/Set/Remove commands for libraries on Targets.  Return credentials on AuthorizeSession request.

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>

Co-authored-by: Louis Ruch <louisruch@gmail.com>
Co-authored-by: Todd Knight <T.Alan.Knight@gmail.com>
Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
This commit is contained in:
authored and GitHub committed 2021-06-22 22:26:26 -04:00
1 parent 8642274a79
commit df35699c4e
216 files changed
+38675 -1941

No files matched your search

+1
View File
@@ -946,6 +946,7 @@ jobs:
command: |
make test-ci
name: Run Acceptance Tests
no_output_timeout: 30m
- run:
command: |
echo 'export SLACK_BUILD_STATUS="fail"' >> $BASH_ENV
+1
View File
@@ -5,6 +5,7 @@ steps:
- checkout
- run:
name: "Run Acceptance Tests"
no_output_timeout: 15m
command: |
make test-ci
- slack/status:
+2
View File
@@ -134,6 +134,8 @@ protobuild:
@protoc-go-inject-tag -input=./internal/target/store/target.pb.go
@protoc-go-inject-tag -input=./internal/auth/oidc/store/oidc.pb.go
@protoc-go-inject-tag -input=./internal/scheduler/job/store/job.pb.go
@protoc-go-inject-tag -input=./internal/credential/store/credential.pb.go
@protoc-go-inject-tag -input=./internal/credential/vault/store/vault.pb.go
@rm -R ${TMP_DIR}
+13 -13
View File
@@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, authMethodId string, opt ...Option)
return target, nil
}
func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*AccountReadResult, error) {
if accountId == "" {
return nil, fmt.Errorf("empty accountId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AccountReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*Ac
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("accounts/%s", accountId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("accounts/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*Ac
return target, nil
}
func (c *Client) Update(ctx context.Context, accountId string, version uint32, opt ...Option) (*AccountUpdateResult, error) {
if accountId == "" {
return nil, fmt.Errorf("empty accountId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*AccountUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, accountId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("accounts/%s", accountId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("accounts/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o
return target, nil
}
func (c *Client) Delete(ctx context.Context, accountId string, opt ...Option) (*AccountDeleteResult, error) {
if accountId == "" {
return nil, fmt.Errorf("empty accountId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AccountDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, accountId string, opt ...Option) (*
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("accounts/%s", accountId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("accounts/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+13 -13
View File
@@ -141,9 +141,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string
return target, nil
}
func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethodReadResult, error) {
if authMethodId == "" {
return nil, fmt.Errorf("empty authMethodId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AuthMethodReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -151,7 +151,7 @@ func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) (
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-methods/%s", authMethodId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-methods/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -182,9 +182,9 @@ func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) (
return target, nil
}
func (c *Client) Update(ctx context.Context, authMethodId string, version uint32, opt ...Option) (*AuthMethodUpdateResult, error) {
if authMethodId == "" {
return nil, fmt.Errorf("empty authMethodId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*AuthMethodUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -196,7 +196,7 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, authMethodId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -214,7 +214,7 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("auth-methods/%s", authMethodId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("auth-methods/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -245,9 +245,9 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32
return target, nil
}
func (c *Client) Delete(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethodDeleteResult, error) {
if authMethodId == "" {
return nil, fmt.Errorf("empty authMethodId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AuthMethodDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -255,7 +255,7 @@ func (c *Client) Delete(ctx context.Context, authMethodId string, opt ...Option)
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-methods/%s", authMethodId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-methods/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+8 -8
View File
@@ -90,9 +90,9 @@ func (c *Client) ApiClient() *api.Client {
return c.client
}
func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (*AuthTokenReadResult, error) {
if authTokenId == "" {
return nil, fmt.Errorf("empty authTokenId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AuthTokenReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -100,7 +100,7 @@ func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (*
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-tokens/%s", authTokenId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-tokens/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -131,9 +131,9 @@ func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (*
return target, nil
}
func (c *Client) Delete(ctx context.Context, authTokenId string, opt ...Option) (*AuthTokenDeleteResult, error) {
if authTokenId == "" {
return nil, fmt.Errorf("empty authTokenId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AuthTokenDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -141,7 +141,7 @@ func (c *Client) Delete(ctx context.Context, authTokenId string, opt ...Option)
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-tokens/%s", authTokenId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-tokens/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -0,0 +1,322 @@
// Code generated by "make api"; DO NOT EDIT.
package credentiallibraries
import (
"context"
"errors"
"fmt"
"net/url"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/scopes"
)
type CredentialLibrary struct {
Id string `json:"id,omitempty"`
CredentialStoreId string `json:"credential_store_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UpdatedTime time.Time `json:"updated_time,omitempty"`
Version uint32 `json:"version,omitempty"`
Type string `json:"type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
response *api.Response
}
type CredentialLibraryReadResult struct {
Item *CredentialLibrary
response *api.Response
}
func (n CredentialLibraryReadResult) GetItem() interface{} {
return n.Item
}
func (n CredentialLibraryReadResult) GetResponse() *api.Response {
return n.response
}
type (
CredentialLibraryCreateResult = CredentialLibraryReadResult
CredentialLibraryUpdateResult = CredentialLibraryReadResult
)
type CredentialLibraryDeleteResult struct {
response *api.Response
}
// GetItem will always be nil for CredentialLibraryDeleteResult
func (n CredentialLibraryDeleteResult) GetItem() interface{} {
return nil
}
func (n CredentialLibraryDeleteResult) GetResponse() *api.Response {
return n.response
}
type CredentialLibraryListResult struct {
Items []*CredentialLibrary
response *api.Response
}
func (n CredentialLibraryListResult) GetItems() interface{} {
return n.Items
}
func (n CredentialLibraryListResult) GetResponse() *api.Response {
return n.response
}
// Client is a client for this collection
type Client struct {
client *api.Client
}
// Creates a new client for this collection. The submitted API client is cloned;
// modifications to it after generating this client will not have effect. If you
// need to make changes to the underlying API client, use ApiClient() to access
// it.
func NewClient(c *api.Client) *Client {
return &Client{client: c.Clone()}
}
// ApiClient returns the underlying API client
func (c *Client) ApiClient() *api.Client {
return c.client
}
func (c *Client) Create(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialLibraryCreateResult, error) {
if credentialStoreId == "" {
return nil, fmt.Errorf("empty credentialStoreId value passed into Create request")
}
opts, apiOpts := getOpts(opt...)
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts.postMap["credential_store_id"] = credentialStoreId
req, err := c.client.NewRequest(ctx, "POST", "credential-libraries", opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Create request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Create call: %w", err)
}
target := new(CredentialLibraryCreateResult)
target.Item = new(CredentialLibrary)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Create response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*CredentialLibraryReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("credential-libraries/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error performing client request during Read call: %w", err)
}
target := new(CredentialLibraryReadResult)
target.Item = new(CredentialLibrary)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Read response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*CredentialLibraryUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingTarget == nil {
return nil, errors.New("nil resource response found when performing initial check-and-set read")
}
if existingTarget.Item == nil {
return nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Item.Version
}
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("credential-libraries/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Update call: %w", err)
}
target := new(CredentialLibraryUpdateResult)
target.Item = new(CredentialLibrary)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Update response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*CredentialLibraryDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("credential-libraries/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Delete call: %w", err)
}
apiErr, err := resp.Decode(nil)
if err != nil {
return nil, fmt.Errorf("error decoding Delete response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target := &CredentialLibraryDeleteResult{
response: resp,
}
return target, nil
}
func (c *Client) List(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialLibraryListResult, error) {
if credentialStoreId == "" {
return nil, fmt.Errorf("empty credentialStoreId value passed into List request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
opts.queryMap["credential_store_id"] = credentialStoreId
req, err := c.client.NewRequest(ctx, "GET", "credential-libraries", nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating List request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during List call: %w", err)
}
target := new(CredentialLibraryListResult)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, fmt.Errorf("error decoding List response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
+171
View File
@@ -0,0 +1,171 @@
package credentiallibraries
import (
"strings"
"github.com/hashicorp/boundary/api"
)
// Option is a func that sets optional attributes for a call. This does not need
// to be used directly, but instead option arguments are built from the
// functions in this package. WithX options set a value to that given in the
// argument; DefaultX options indicate that the value should be set to its
// default. When an API call is made options are processed in ther order they
// appear in the function call, so for a given argument X, a succession of WithX
// or DefaultX calls will result in the last call taking effect.
type Option func(*options)
type options struct {
postMap map[string]interface{}
queryMap map[string]string
withAutomaticVersioning bool
withSkipCurlOutput bool
withFilter string
}
func getDefaultOptions() options {
return options{
postMap: make(map[string]interface{}),
queryMap: make(map[string]string),
}
}
func getOpts(opt ...Option) (options, []api.Option) {
opts := getDefaultOptions()
for _, o := range opt {
if o != nil {
o(&opts)
}
}
var apiOpts []api.Option
if opts.withSkipCurlOutput {
apiOpts = append(apiOpts, api.WithSkipCurlOutput(true))
}
if opts.withFilter != "" {
opts.queryMap["filter"] = opts.withFilter
}
return opts, apiOpts
}
// If set, and if the version is zero during an update, the API will perform a
// fetch to get the current version of the resource and populate it during the
// update call. This is convenient but opens up the possibility for subtle
// order-of-modification issues, so use carefully.
func WithAutomaticVersioning(enable bool) Option {
return func(o *options) {
o.withAutomaticVersioning = enable
}
}
// WithSkipCurlOutput tells the API to not use the current call for cURL output.
// Useful for when we need to look up versions.
func WithSkipCurlOutput(skip bool) Option {
return func(o *options) {
o.withSkipCurlOutput = true
}
}
// WithFilter tells the API to filter the items returned using the provided
// filter term. The filter should be in a format supported by
// hashicorp/go-bexpr.
func WithFilter(filter string) Option {
return func(o *options) {
o.withFilter = strings.TrimSpace(filter)
}
}
func WithAttributes(inAttributes map[string]interface{}) Option {
return func(o *options) {
o.postMap["attributes"] = inAttributes
}
}
func DefaultAttributes() Option {
return func(o *options) {
o.postMap["attributes"] = nil
}
}
func WithDescription(inDescription string) Option {
return func(o *options) {
o.postMap["description"] = inDescription
}
}
func DefaultDescription() Option {
return func(o *options) {
o.postMap["description"] = nil
}
}
func WithVaultCredentialLibraryHttpMethod(inHttpMethod string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["http_method"] = inHttpMethod
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialLibraryHttpMethod() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["http_method"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialLibraryHttpRequestBody(inHttpRequestBody string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["http_request_body"] = inHttpRequestBody
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialLibraryHttpRequestBody() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["http_request_body"] = nil
o.postMap["attributes"] = val
}
}
func WithName(inName string) Option {
return func(o *options) {
o.postMap["name"] = inName
}
}
func DefaultName() Option {
return func(o *options) {
o.postMap["name"] = nil
}
}
func WithVaultCredentialLibraryPath(inPath string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["path"] = inPath
o.postMap["attributes"] = val
}
}
@@ -0,0 +1,8 @@
// Code generated by "make api"; DO NOT EDIT.
package credentiallibraries
type VaultCredentialLibraryAttributes struct {
Path string `json:"path,omitempty"`
HttpMethod string `json:"http_method,omitempty"`
HttpRequestBody string `json:"http_request_body,omitempty"`
}
@@ -0,0 +1,328 @@
// Code generated by "make api"; DO NOT EDIT.
package credentialstores
import (
"context"
"errors"
"fmt"
"net/url"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/scopes"
)
type CredentialStore struct {
Id string `json:"id,omitempty"`
ScopeId string `json:"scope_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UpdatedTime time.Time `json:"updated_time,omitempty"`
Version uint32 `json:"version,omitempty"`
Type string `json:"type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
AuthorizedCollectionActions map[string][]string `json:"authorized_collection_actions,omitempty"`
response *api.Response
}
type CredentialStoreReadResult struct {
Item *CredentialStore
response *api.Response
}
func (n CredentialStoreReadResult) GetItem() interface{} {
return n.Item
}
func (n CredentialStoreReadResult) GetResponse() *api.Response {
return n.response
}
type (
CredentialStoreCreateResult = CredentialStoreReadResult
CredentialStoreUpdateResult = CredentialStoreReadResult
)
type CredentialStoreDeleteResult struct {
response *api.Response
}
// GetItem will always be nil for CredentialStoreDeleteResult
func (n CredentialStoreDeleteResult) GetItem() interface{} {
return nil
}
func (n CredentialStoreDeleteResult) GetResponse() *api.Response {
return n.response
}
type CredentialStoreListResult struct {
Items []*CredentialStore
response *api.Response
}
func (n CredentialStoreListResult) GetItems() interface{} {
return n.Items
}
func (n CredentialStoreListResult) GetResponse() *api.Response {
return n.response
}
// Client is a client for this collection
type Client struct {
client *api.Client
}
// Creates a new client for this collection. The submitted API client is cloned;
// modifications to it after generating this client will not have effect. If you
// need to make changes to the underlying API client, use ApiClient() to access
// it.
func NewClient(c *api.Client) *Client {
return &Client{client: c.Clone()}
}
// ApiClient returns the underlying API client
func (c *Client) ApiClient() *api.Client {
return c.client
}
func (c *Client) Create(ctx context.Context, resourceType string, scopeId string, opt ...Option) (*CredentialStoreCreateResult, error) {
if scopeId == "" {
return nil, fmt.Errorf("empty scopeId value passed into Create request")
}
opts, apiOpts := getOpts(opt...)
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
if resourceType == "" {
return nil, fmt.Errorf("empty resourceType value passed into Create request")
} else {
opts.postMap["type"] = resourceType
}
opts.postMap["scope_id"] = scopeId
req, err := c.client.NewRequest(ctx, "POST", "credential-stores", opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Create request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Create call: %w", err)
}
target := new(CredentialStoreCreateResult)
target.Item = new(CredentialStore)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Create response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*CredentialStoreReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("credential-stores/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error performing client request during Read call: %w", err)
}
target := new(CredentialStoreReadResult)
target.Item = new(CredentialStore)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Read response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*CredentialStoreUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingTarget == nil {
return nil, errors.New("nil resource response found when performing initial check-and-set read")
}
if existingTarget.Item == nil {
return nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Item.Version
}
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("credential-stores/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Update call: %w", err)
}
target := new(CredentialStoreUpdateResult)
target.Item = new(CredentialStore)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding Update response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*CredentialStoreDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("credential-stores/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during Delete call: %w", err)
}
apiErr, err := resp.Decode(nil)
if err != nil {
return nil, fmt.Errorf("error decoding Delete response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target := &CredentialStoreDeleteResult{
response: resp,
}
return target, nil
}
func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*CredentialStoreListResult, error) {
if scopeId == "" {
return nil, fmt.Errorf("empty scopeId value passed into List request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
opts.queryMap["scope_id"] = scopeId
req, err := c.client.NewRequest(ctx, "GET", "credential-stores", nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating List request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during List call: %w", err)
}
target := new(CredentialStoreListResult)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, fmt.Errorf("error decoding List response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
+292
View File
@@ -0,0 +1,292 @@
package credentialstores
import (
"strconv"
"strings"
"github.com/hashicorp/boundary/api"
)
// Option is a func that sets optional attributes for a call. This does not need
// to be used directly, but instead option arguments are built from the
// functions in this package. WithX options set a value to that given in the
// argument; DefaultX options indicate that the value should be set to its
// default. When an API call is made options are processed in ther order they
// appear in the function call, so for a given argument X, a succession of WithX
// or DefaultX calls will result in the last call taking effect.
type Option func(*options)
type options struct {
postMap map[string]interface{}
queryMap map[string]string
withAutomaticVersioning bool
withSkipCurlOutput bool
withFilter string
withRecursive bool
}
func getDefaultOptions() options {
return options{
postMap: make(map[string]interface{}),
queryMap: make(map[string]string),
}
}
func getOpts(opt ...Option) (options, []api.Option) {
opts := getDefaultOptions()
for _, o := range opt {
if o != nil {
o(&opts)
}
}
var apiOpts []api.Option
if opts.withSkipCurlOutput {
apiOpts = append(apiOpts, api.WithSkipCurlOutput(true))
}
if opts.withFilter != "" {
opts.queryMap["filter"] = opts.withFilter
}
if opts.withRecursive {
opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive)
}
return opts, apiOpts
}
// If set, and if the version is zero during an update, the API will perform a
// fetch to get the current version of the resource and populate it during the
// update call. This is convenient but opens up the possibility for subtle
// order-of-modification issues, so use carefully.
func WithAutomaticVersioning(enable bool) Option {
return func(o *options) {
o.withAutomaticVersioning = enable
}
}
// WithSkipCurlOutput tells the API to not use the current call for cURL output.
// Useful for when we need to look up versions.
func WithSkipCurlOutput(skip bool) Option {
return func(o *options) {
o.withSkipCurlOutput = true
}
}
// WithFilter tells the API to filter the items returned using the provided
// filter term. The filter should be in a format supported by
// hashicorp/go-bexpr.
func WithFilter(filter string) Option {
return func(o *options) {
o.withFilter = strings.TrimSpace(filter)
}
}
// WithRecursive tells the API to use recursion for listing operations on this
// resource
func WithRecursive(recurse bool) Option {
return func(o *options) {
o.withRecursive = true
}
}
func WithVaultCredentialStoreAddress(inAddress string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["address"] = inAddress
o.postMap["attributes"] = val
}
}
func WithAttributes(inAttributes map[string]interface{}) Option {
return func(o *options) {
o.postMap["attributes"] = inAttributes
}
}
func DefaultAttributes() Option {
return func(o *options) {
o.postMap["attributes"] = nil
}
}
func WithVaultCredentialStoreCaCert(inCaCert string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["ca_cert"] = inCaCert
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreCaCert() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["ca_cert"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialStoreClientCertificate(inClientCertificate string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["client_certificate"] = inClientCertificate
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreClientCertificate() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["client_certificate"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialStoreClientCertificateKey(inClientCertificateKey string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["client_certificate_key"] = inClientCertificateKey
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreClientCertificateKey() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["client_certificate_key"] = nil
o.postMap["attributes"] = val
}
}
func WithDescription(inDescription string) Option {
return func(o *options) {
o.postMap["description"] = inDescription
}
}
func DefaultDescription() Option {
return func(o *options) {
o.postMap["description"] = nil
}
}
func WithName(inName string) Option {
return func(o *options) {
o.postMap["name"] = inName
}
}
func DefaultName() Option {
return func(o *options) {
o.postMap["name"] = nil
}
}
func WithVaultCredentialStoreNamespace(inNamespace string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["namespace"] = inNamespace
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreNamespace() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["namespace"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialStoreTlsServerName(inTlsServerName string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["tls_server_name"] = inTlsServerName
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreTlsServerName() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["tls_server_name"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialStoreTlsSkipVerify(inTlsSkipVerify bool) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["tls_skip_verify"] = inTlsSkipVerify
o.postMap["attributes"] = val
}
}
func DefaultVaultCredentialStoreTlsSkipVerify() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["tls_skip_verify"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialStoreToken(inToken string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["token"] = inToken
o.postMap["attributes"] = val
}
}
@@ -0,0 +1,15 @@
// Code generated by "make api"; DO NOT EDIT.
package credentialstores
type VaultCredentialStoreAttributes struct {
Address string `json:"address,omitempty"`
Namespace string `json:"namespace,omitempty"`
CaCert string `json:"ca_cert,omitempty"`
TlsServerName string `json:"tls_server_name,omitempty"`
TlsSkipVerify bool `json:"tls_skip_verify,omitempty"`
Token string `json:"token,omitempty"`
TokenHmac string `json:"token_hmac,omitempty"`
ClientCertificate string `json:"client_certificate,omitempty"`
ClientCertificateKey string `json:"client_certificate_key,omitempty"`
ClientCertificateKeyHmac string `json:"client_certificate_key_hmac,omitempty"`
}
+28 -28
View File
@@ -134,9 +134,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Gr
return target, nil
}
func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*GroupReadResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*GroupReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -144,7 +144,7 @@ func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*Grou
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("groups/%s", groupId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("groups/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -175,9 +175,9 @@ func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*Grou
return target, nil
}
func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt ...Option) (*GroupUpdateResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*GroupUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -189,7 +189,7 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -207,7 +207,7 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("groups/%s", groupId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("groups/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -238,9 +238,9 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt
return target, nil
}
func (c *Client) Delete(ctx context.Context, groupId string, opt ...Option) (*GroupDeleteResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*GroupDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -248,7 +248,7 @@ func (c *Client) Delete(ctx context.Context, groupId string, opt ...Option) (*Gr
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("groups/%s", groupId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("groups/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -321,9 +321,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Grou
return target, nil
}
func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into AddMembers request")
func (c *Client) AddMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddMembers request")
}
if len(memberIds) == 0 {
return nil, errors.New("empty memberIds passed into AddMembers request")
@@ -338,7 +338,7 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddMembers request")
}
existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -357,7 +357,7 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32,
opts.postMap["version"] = version
opts.postMap["member_ids"] = memberIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:add-members", groupId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:add-members", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddMembers request: %w", err)
}
@@ -388,9 +388,9 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32,
return target, nil
}
func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into SetMembers request")
func (c *Client) SetMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetMembers request")
}
if c.client == nil {
@@ -403,7 +403,7 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetMembers request")
}
existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -422,7 +422,7 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32,
opts.postMap["version"] = version
opts.postMap["member_ids"] = memberIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:set-members", groupId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:set-members", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetMembers request: %w", err)
}
@@ -453,9 +453,9 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32,
return target, nil
}
func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if groupId == "" {
return nil, fmt.Errorf("empty groupId value passed into RemoveMembers request")
func (c *Client) RemoveMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveMembers request")
}
if len(memberIds) == 0 {
return nil, errors.New("empty memberIds passed into RemoveMembers request")
@@ -470,7 +470,7 @@ func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveMembers request")
}
existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -489,7 +489,7 @@ func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint
opts.postMap["version"] = version
opts.postMap["member_ids"] = memberIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:remove-members", groupId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:remove-members", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveMembers request: %w", err)
}
+13 -13
View File
@@ -140,9 +140,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string
return target, nil
}
func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option) (*HostCatalogReadResult, error) {
if hostCatalogId == "" {
return nil, fmt.Errorf("empty hostCatalogId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostCatalogReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -150,7 +150,7 @@ func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option)
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-catalogs/%s", hostCatalogId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-catalogs/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -181,9 +181,9 @@ func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option)
return target, nil
}
func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint32, opt ...Option) (*HostCatalogUpdateResult, error) {
if hostCatalogId == "" {
return nil, fmt.Errorf("empty hostCatalogId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostCatalogUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -195,7 +195,7 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, hostCatalogId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -213,7 +213,7 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-catalogs/%s", hostCatalogId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-catalogs/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -244,9 +244,9 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3
return target, nil
}
func (c *Client) Delete(ctx context.Context, hostCatalogId string, opt ...Option) (*HostCatalogDeleteResult, error) {
if hostCatalogId == "" {
return nil, fmt.Errorf("empty hostCatalogId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostCatalogDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -254,7 +254,7 @@ func (c *Client) Delete(ctx context.Context, hostCatalogId string, opt ...Option
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-catalogs/%s", hostCatalogId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-catalogs/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+13 -13
View File
@@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, hostCatalogId string, opt ...Option
return target, nil
}
func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostReadResult, error) {
if hostId == "" {
return nil, fmt.Errorf("empty hostId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostR
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("hosts/%s", hostId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("hosts/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostR
return target, nil
}
func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt ...Option) (*HostUpdateResult, error) {
if hostId == "" {
return nil, fmt.Errorf("empty hostId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, hostId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("hosts/%s", hostId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("hosts/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt
return target, nil
}
func (c *Client) Delete(ctx context.Context, hostId string, opt ...Option) (*HostDeleteResult, error) {
if hostId == "" {
return nil, fmt.Errorf("empty hostId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, hostId string, opt ...Option) (*Hos
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("hosts/%s", hostId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("hosts/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+28 -28
View File
@@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, hostCatalogId string, opt ...Option
return target, nil
}
func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*HostSetReadResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostSetReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*Ho
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-sets/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*Ho
return target, nil
}
func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, opt ...Option) (*HostSetUpdateResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostSetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-sets/%s", hostSetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-sets/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o
return target, nil
}
func (c *Client) Delete(ctx context.Context, hostSetId string, opt ...Option) (*HostSetDeleteResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostSetDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, hostSetId string, opt ...Option) (*
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-sets/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -322,9 +322,9 @@ func (c *Client) List(ctx context.Context, hostCatalogId string, opt ...Option)
return target, nil
}
func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into AddHosts request")
func (c *Client) AddHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddHosts request")
}
if len(hostIds) == 0 {
return nil, errors.New("empty hostIds passed into AddHosts request")
@@ -339,7 +339,7 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddHosts request")
}
existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -358,7 +358,7 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32,
opts.postMap["version"] = version
opts.postMap["host_ids"] = hostIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:add-hosts", hostSetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:add-hosts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddHosts request: %w", err)
}
@@ -389,9 +389,9 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32,
return target, nil
}
func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into SetHosts request")
func (c *Client) SetHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetHosts request")
}
if c.client == nil {
@@ -404,7 +404,7 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetHosts request")
}
existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -423,7 +423,7 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32,
opts.postMap["version"] = version
opts.postMap["host_ids"] = hostIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:set-hosts", hostSetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:set-hosts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetHosts request: %w", err)
}
@@ -454,9 +454,9 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32,
return target, nil
}
func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if hostSetId == "" {
return nil, fmt.Errorf("empty hostSetId value passed into RemoveHosts request")
func (c *Client) RemoveHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveHosts request")
}
if len(hostIds) == 0 {
return nil, errors.New("empty hostIds passed into RemoveHosts request")
@@ -471,7 +471,7 @@ func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveHosts request")
}
existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -490,7 +490,7 @@ func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint
opts.postMap["version"] = version
opts.postMap["host_ids"] = hostIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:remove-hosts", hostSetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:remove-hosts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveHosts request: %w", err)
}
+13 -13
View File
@@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, authMethodId string, opt ...Option)
return target, nil
}
func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupReadResult, error) {
if managedGroupId == "" {
return nil, fmt.Errorf("empty managedGroupId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*ManagedGroupReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option)
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("managed-groups/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option)
return target, nil
}
func (c *Client) Update(ctx context.Context, managedGroupId string, version uint32, opt ...Option) (*ManagedGroupUpdateResult, error) {
if managedGroupId == "" {
return nil, fmt.Errorf("empty managedGroupId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*ManagedGroupUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, managedGroupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("managed-groups/%s", managedGroupId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("managed-groups/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint
return target, nil
}
func (c *Client) Delete(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupDeleteResult, error) {
if managedGroupId == "" {
return nil, fmt.Errorf("empty managedGroupId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*ManagedGroupDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, managedGroupId string, opt ...Optio
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("managed-groups/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+43 -43
View File
@@ -137,9 +137,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Ro
return target, nil
}
func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleReadResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*RoleReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -147,7 +147,7 @@ func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleR
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("roles/%s", roleId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("roles/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -178,9 +178,9 @@ func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleR
return target, nil
}
func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -192,7 +192,7 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -210,7 +210,7 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("roles/%s", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("roles/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -241,9 +241,9 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt
return target, nil
}
func (c *Client) Delete(ctx context.Context, roleId string, opt ...Option) (*RoleDeleteResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*RoleDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -251,7 +251,7 @@ func (c *Client) Delete(ctx context.Context, roleId string, opt ...Option) (*Rol
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("roles/%s", roleId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("roles/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -324,9 +324,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Role
return target, nil
}
func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into AddGrants request")
func (c *Client) AddGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddGrants request")
}
if len(grantStrings) == 0 {
return nil, errors.New("empty grantStrings passed into AddGrants request")
@@ -341,7 +341,7 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddGrants request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -360,7 +360,7 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g
opts.postMap["version"] = version
opts.postMap["grant_strings"] = grantStrings
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grants", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grants", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddGrants request: %w", err)
}
@@ -391,9 +391,9 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g
return target, nil
}
func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into AddPrincipals request")
func (c *Client) AddPrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddPrincipals request")
}
if len(principalIds) == 0 {
return nil, errors.New("empty principalIds passed into AddPrincipals request")
@@ -408,7 +408,7 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddPrincipals request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -427,7 +427,7 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3
opts.postMap["version"] = version
opts.postMap["principal_ids"] = principalIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-principals", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-principals", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddPrincipals request: %w", err)
}
@@ -458,9 +458,9 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3
return target, nil
}
func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into SetGrants request")
func (c *Client) SetGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetGrants request")
}
if c.client == nil {
@@ -473,7 +473,7 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetGrants request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -492,7 +492,7 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g
opts.postMap["version"] = version
opts.postMap["grant_strings"] = grantStrings
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grants", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grants", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetGrants request: %w", err)
}
@@ -523,9 +523,9 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g
return target, nil
}
func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into SetPrincipals request")
func (c *Client) SetPrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetPrincipals request")
}
if c.client == nil {
@@ -538,7 +538,7 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetPrincipals request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -557,7 +557,7 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3
opts.postMap["version"] = version
opts.postMap["principal_ids"] = principalIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-principals", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-principals", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetPrincipals request: %w", err)
}
@@ -588,9 +588,9 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3
return target, nil
}
func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into RemoveGrants request")
func (c *Client) RemoveGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveGrants request")
}
if len(grantStrings) == 0 {
return nil, errors.New("empty grantStrings passed into RemoveGrants request")
@@ -605,7 +605,7 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveGrants request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -624,7 +624,7 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32
opts.postMap["version"] = version
opts.postMap["grant_strings"] = grantStrings
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grants", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grants", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveGrants request: %w", err)
}
@@ -655,9 +655,9 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32
return target, nil
}
func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if roleId == "" {
return nil, fmt.Errorf("empty roleId value passed into RemovePrincipals request")
func (c *Client) RemovePrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemovePrincipals request")
}
if len(principalIds) == 0 {
return nil, errors.New("empty principalIds passed into RemovePrincipals request")
@@ -672,7 +672,7 @@ func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version ui
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemovePrincipals request")
}
existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -691,7 +691,7 @@ func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version ui
opts.postMap["version"] = version
opts.postMap["principal_ids"] = principalIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-principals", roleId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-principals", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemovePrincipals request: %w", err)
}
+13 -13
View File
@@ -134,9 +134,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Sc
return target, nil
}
func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*ScopeReadResult, error) {
if scopeId == "" {
return nil, fmt.Errorf("empty scopeId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*ScopeReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -144,7 +144,7 @@ func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*Scop
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("scopes/%s", scopeId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("scopes/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -175,9 +175,9 @@ func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*Scop
return target, nil
}
func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt ...Option) (*ScopeUpdateResult, error) {
if scopeId == "" {
return nil, fmt.Errorf("empty scopeId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*ScopeUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -189,7 +189,7 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, scopeId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -207,7 +207,7 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("scopes/%s", scopeId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("scopes/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -238,9 +238,9 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt
return target, nil
}
func (c *Client) Delete(ctx context.Context, scopeId string, opt ...Option) (*ScopeDeleteResult, error) {
if scopeId == "" {
return nil, fmt.Errorf("empty scopeId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*ScopeDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -248,7 +248,7 @@ func (c *Client) Delete(ctx context.Context, scopeId string, opt ...Option) (*Sc
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("scopes/%s", scopeId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("scopes/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
+4 -4
View File
@@ -98,9 +98,9 @@ func (c *Client) ApiClient() *api.Client {
return c.client
}
func (c *Client) Read(ctx context.Context, sessionId string, opt ...Option) (*SessionReadResult, error) {
if sessionId == "" {
return nil, fmt.Errorf("empty sessionId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*SessionReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -108,7 +108,7 @@ func (c *Client) Read(ctx context.Context, sessionId string, opt ...Option) (*Se
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("sessions/%s", sessionId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("sessions/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
+11
View File
@@ -0,0 +1,11 @@
// Code generated by "make api"; DO NOT EDIT.
package targets
type CredentialLibrary struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CredentialStoreId string `json:"credential_store_id,omitempty"`
Purpose string `json:"purpose,omitempty"`
Type string `json:"type,omitempty"`
}
+11 -10
View File
@@ -8,14 +8,15 @@ import (
)
type SessionAuthorization struct {
SessionId string `json:"session_id,omitempty"`
TargetId string `json:"target_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UserId string `json:"user_id,omitempty"`
HostSetId string `json:"host_set_id,omitempty"`
HostId string `json:"host_id,omitempty"`
Type string `json:"type,omitempty"`
AuthorizationToken string `json:"authorization_token,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
SessionId string `json:"session_id,omitempty"`
TargetId string `json:"target_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UserId string `json:"user_id,omitempty"`
HostSetId string `json:"host_set_id,omitempty"`
HostId string `json:"host_id,omitempty"`
Type string `json:"type,omitempty"`
AuthorizationToken string `json:"authorization_token,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Credentials []*SessionCredential `json:"credentials,omitempty"`
}
+7
View File
@@ -0,0 +1,7 @@
// Code generated by "make api"; DO NOT EDIT.
package targets
type SessionCredential struct {
CredentialLibrary *CredentialLibrary `json:"credential_library,omitempty"`
Secret string `json:"secret,omitempty"`
}
+229 -28
View File
@@ -27,6 +27,8 @@ type Target struct {
SessionMaxSeconds uint32 `json:"session_max_seconds,omitempty"`
SessionConnectionLimit int32 `json:"session_connection_limit,omitempty"`
WorkerFilter string `json:"worker_filter,omitempty"`
CredentialLibraryIds []string `json:"credential_library_ids,omitempty"`
CredentialLibraries []*CredentialLibrary `json:"credential_libraries,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
@@ -144,9 +146,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string
return target, nil
}
func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*TargetReadResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*TargetReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -154,7 +156,7 @@ func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*Tar
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("targets/%s", targetId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("targets/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -185,9 +187,9 @@ func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*Tar
return target, nil
}
func (c *Client) Update(ctx context.Context, targetId string, version uint32, opt ...Option) (*TargetUpdateResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -199,7 +201,7 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -217,7 +219,7 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("targets/%s", targetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("targets/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -248,9 +250,9 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op
return target, nil
}
func (c *Client) Delete(ctx context.Context, targetId string, opt ...Option) (*TargetDeleteResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*TargetDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -258,7 +260,7 @@ func (c *Client) Delete(ctx context.Context, targetId string, opt ...Option) (*T
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("targets/%s", targetId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("targets/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -331,9 +333,76 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Targ
return target, nil
}
func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into AddHostSets request")
func (c *Client) AddCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddCredentialLibraries request")
}
if len(credentialLibraryIds) == 0 {
return nil, errors.New("empty credentialLibraryIds passed into AddCredentialLibraries request")
}
if c.client == nil {
return nil, errors.New("nil client")
}
opts, apiOpts := getOpts(opt...)
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddCredentialLibraries request")
}
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingTarget == nil {
return nil, errors.New("nil resource response found when performing initial check-and-set read")
}
if existingTarget.Item == nil {
return nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Item.Version
}
opts.postMap["version"] = version
opts.postMap["credential_library_ids"] = credentialLibraryIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-credential-libraries", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddCredentialLibraries request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during AddCredentialLibraries call: %w", err)
}
target := new(TargetUpdateResult)
target.Item = new(Target)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding AddCredentialLibraries response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) AddHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddHostSets request")
}
if len(hostSetIds) == 0 {
return nil, errors.New("empty hostSetIds passed into AddHostSets request")
@@ -348,7 +417,7 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddHostSets request")
}
existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -367,7 +436,7 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3
opts.postMap["version"] = version
opts.postMap["host_set_ids"] = hostSetIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-host-sets", targetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-host-sets", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddHostSets request: %w", err)
}
@@ -398,9 +467,74 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3
return target, nil
}
func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into SetHostSets request")
func (c *Client) SetCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetCredentialLibraries request")
}
if c.client == nil {
return nil, errors.New("nil client")
}
opts, apiOpts := getOpts(opt...)
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetCredentialLibraries request")
}
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingTarget == nil {
return nil, errors.New("nil resource response found when performing initial check-and-set read")
}
if existingTarget.Item == nil {
return nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Item.Version
}
opts.postMap["version"] = version
opts.postMap["credential_library_ids"] = credentialLibraryIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-credential-libraries", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetCredentialLibraries request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during SetCredentialLibraries call: %w", err)
}
target := new(TargetUpdateResult)
target.Item = new(Target)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding SetCredentialLibraries response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) SetHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetHostSets request")
}
if c.client == nil {
@@ -413,7 +547,7 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetHostSets request")
}
existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -432,7 +566,7 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3
opts.postMap["version"] = version
opts.postMap["host_set_ids"] = hostSetIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-host-sets", targetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-host-sets", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetHostSets request: %w", err)
}
@@ -463,9 +597,76 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3
return target, nil
}
func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if targetId == "" {
return nil, fmt.Errorf("empty targetId value passed into RemoveHostSets request")
func (c *Client) RemoveCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveCredentialLibraries request")
}
if len(credentialLibraryIds) == 0 {
return nil, errors.New("empty credentialLibraryIds passed into RemoveCredentialLibraries request")
}
if c.client == nil {
return nil, errors.New("nil client")
}
opts, apiOpts := getOpts(opt...)
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveCredentialLibraries request")
}
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingTarget == nil {
return nil, errors.New("nil resource response found when performing initial check-and-set read")
}
if existingTarget.Item == nil {
return nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Item.Version
}
opts.postMap["version"] = version
opts.postMap["credential_library_ids"] = credentialLibraryIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-credential-libraries", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveCredentialLibraries request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error performing client request during RemoveCredentialLibraries call: %w", err)
}
target := new(TargetUpdateResult)
target.Item = new(Target)
apiErr, err := resp.Decode(target.Item)
if err != nil {
return nil, fmt.Errorf("error decoding RemoveCredentialLibraries response: %w", err)
}
if apiErr != nil {
return nil, apiErr
}
target.response = resp
return target, nil
}
func (c *Client) RemoveHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveHostSets request")
}
if len(hostSetIds) == 0 {
return nil, errors.New("empty hostSetIds passed into RemoveHostSets request")
@@ -480,7 +681,7 @@ func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version ui
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveHostSets request")
}
existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -499,7 +700,7 @@ func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version ui
opts.postMap["version"] = version
opts.postMap["host_set_ids"] = hostSetIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-host-sets", targetId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-host-sets", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveHostSets request: %w", err)
}
+28 -28
View File
@@ -138,9 +138,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Us
return target, nil
}
func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserReadResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*UserReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -148,7 +148,7 @@ func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserR
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("users/%s", userId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("users/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Read request: %w", err)
}
@@ -179,9 +179,9 @@ func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserR
return target, nil
}
func (c *Client) Update(ctx context.Context, userId string, version uint32, opt ...Option) (*UserUpdateResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*UserUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -193,7 +193,7 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -211,7 +211,7 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("users/%s", userId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("users/%s", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Update request: %w", err)
}
@@ -242,9 +242,9 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt
return target, nil
}
func (c *Client) Delete(ctx context.Context, userId string, opt ...Option) (*UserDeleteResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*UserDeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -252,7 +252,7 @@ func (c *Client) Delete(ctx context.Context, userId string, opt ...Option) (*Use
opts, apiOpts := getOpts(opt...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("users/%s", userId), nil, apiOpts...)
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("users/%s", id), nil, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating Delete request: %w", err)
}
@@ -325,9 +325,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*User
return target, nil
}
func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into AddAccounts request")
func (c *Client) AddAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into AddAccounts request")
}
if len(accountIds) == 0 {
return nil, errors.New("empty accountIds passed into AddAccounts request")
@@ -342,7 +342,7 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into AddAccounts request")
}
existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -361,7 +361,7 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32,
opts.postMap["version"] = version
opts.postMap["account_ids"] = accountIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:add-accounts", userId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:add-accounts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating AddAccounts request: %w", err)
}
@@ -392,9 +392,9 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32,
return target, nil
}
func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into SetAccounts request")
func (c *Client) SetAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into SetAccounts request")
}
if c.client == nil {
@@ -407,7 +407,7 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into SetAccounts request")
}
existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -426,7 +426,7 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32,
opts.postMap["version"] = version
opts.postMap["account_ids"] = accountIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:set-accounts", userId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:set-accounts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating SetAccounts request: %w", err)
}
@@ -457,9 +457,9 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32,
return target, nil
}
func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if userId == "" {
return nil, fmt.Errorf("empty userId value passed into RemoveAccounts request")
func (c *Client) RemoveAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into RemoveAccounts request")
}
if len(accountIds) == 0 {
return nil, errors.New("empty accountIds passed into RemoveAccounts request")
@@ -474,7 +474,7 @@ func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into RemoveAccounts request")
}
existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -493,7 +493,7 @@ func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint
opts.postMap["version"] = version
opts.postMap["account_ids"] = accountIds
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:remove-accounts", userId), opts.postMap, apiOpts...)
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:remove-accounts", id), opts.postMap, apiOpts...)
if err != nil {
return nil, fmt.Errorf("error creating RemoveAccounts request: %w", err)
}
+3
View File
@@ -51,4 +51,7 @@ const (
EmailField = "email"
ManagedGroupIdsField = "managed_group_ids"
FilterField = "filter"
CredentialStoreIdField = "credential_store_id"
CredentialLibraryIdsField = "credential_library_ids"
CredentialLibrariesField = "credential_libraries"
)
+2
View File
@@ -29,9 +29,11 @@ require (
github.com/hashicorp/go-kms-wrapping v0.6.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/shared-secure-libs v0.0.6
github.com/hashicorp/vault/api v1.1.0
github.com/hashicorp/vault/sdk v0.2.0
github.com/iancoleman/strcase v0.1.3
github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79
+2 -1
View File
@@ -521,8 +521,9 @@ github.com/hashicorp/shared-secure-libs v0.0.6 h1:QH22a9VbBP3b7v/e5In7uHTanimGPz
github.com/hashicorp/shared-secure-libs v0.0.6/go.mod h1:PH51kNtHA6kYV5Z8E3TOJM634iyp7IIxAMfLh0Dp35o=
github.com/hashicorp/vault/api v1.0.5-0.20200514164214-89b1987e38c2/go.mod h1:P8A7gn1a6j/7qQ4zOaWI6FC/Q9jQWEBRTOwZH33tC9o=
github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE=
github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6 h1:ChHpobQse3kjVX7GmbpUFVqaMTf4lb1gcLrJltsUmS4=
github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
github.com/hashicorp/vault/api v1.1.0 h1:QcxC7FuqEl0sZaIjcXB/kNEeBa0DH5z57qbWBvZwLC4=
github.com/hashicorp/vault/api v1.1.0/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/vault/sdk v0.1.14-0.20200514144402-4bfac290c352/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200514164214-89b1987e38c2/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
+89 -22
View File
@@ -7,6 +7,8 @@ import (
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/accounts"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/groups"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostcatalogs"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts"
@@ -67,13 +69,8 @@ type structInfo struct {
// fields.
versionEnabled bool
// The parameters passed into the path. These should be non-pluralized resource names.
// The templates will convert '-' to '_' and append an _id to them in the SDK param
// and append an 's' to it when building the path.
// The final value should be the path name of the resource since for single resource
// operations all values are used for the function argument.
// For collection based operations the last value is ignored for generating function argument.
pathArgs []string
// This is used for building the api path.
pluralResourceName string
// typeOnCreate indicates that create will be creating a concrete
// implementation of an abstract type and thus a type field is necessary
@@ -138,7 +135,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"scope"},
pluralResourceName: "scopes",
extraOptions: []fieldInfo{
{
Name: "SkipAdminRoleCreation",
@@ -177,7 +174,7 @@ var inputStructs = []*structInfo{
sliceSubtypes: map[string]string{
"Accounts": "accountIds",
},
pathArgs: []string{"user"},
pluralResourceName: "users",
versionEnabled: true,
createResponseTypes: true,
recursiveListing: true,
@@ -202,7 +199,7 @@ var inputStructs = []*structInfo{
sliceSubtypes: map[string]string{
"Members": "memberIds",
},
pathArgs: []string{"group"},
pluralResourceName: "groups",
versionEnabled: true,
createResponseTypes: true,
recursiveListing: true,
@@ -238,7 +235,7 @@ var inputStructs = []*structInfo{
"Principals": "principalIds",
"Grants": "grantStrings",
},
pathArgs: []string{"role"},
pluralResourceName: "roles",
versionEnabled: true,
createResponseTypes: true,
recursiveListing: true,
@@ -270,7 +267,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"auth-method"},
pluralResourceName: "auth-methods",
typeOnCreate: true,
versionEnabled: true,
createResponseTypes: true,
@@ -298,7 +295,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"account"},
pluralResourceName: "accounts",
parentTypeName: "auth-method",
versionEnabled: true,
createResponseTypes: true,
@@ -326,7 +323,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"managed-group"},
pluralResourceName: "managed-groups",
parentTypeName: "auth-method",
versionEnabled: true,
createResponseTypes: true,
@@ -341,10 +338,71 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"auth-token"},
pluralResourceName: "auth-tokens",
createResponseTypes: true,
recursiveListing: true,
},
// Credentials
{
inProto: &credentialstores.VaultCredentialStoreAttributes{},
outFile: "credentialstores/vault_credential_store_attributes.gen.go",
subtypeName: "VaultCredentialStore",
},
{
inProto: &credentialstores.CredentialStore{},
outFile: "credentialstores/credential_store.gen.go",
templates: []*template.Template{
clientTemplate,
createTemplate,
readTemplate,
updateTemplate,
deleteTemplate,
listTemplate,
},
pluralResourceName: "credential-stores",
parentTypeName: "scope",
typeOnCreate: true,
versionEnabled: true,
createResponseTypes: true,
recursiveListing: true,
fieldOverrides: []fieldInfo{
{
Name: "Address",
SkipDefault: true,
},
{
Name: "Token",
SkipDefault: true,
},
},
},
{
inProto: &credentiallibraries.VaultCredentialLibraryAttributes{},
outFile: "credentiallibraries/vault_credential_library_attributes.gen.go",
subtypeName: "VaultCredentialLibrary",
fieldOverrides: []fieldInfo{
{
Name: "Path",
SkipDefault: true,
},
},
},
{
inProto: &credentiallibraries.CredentialLibrary{},
outFile: "credentiallibraries/credential_library.gen.go",
templates: []*template.Template{
clientTemplate,
createTemplate,
readTemplate,
updateTemplate,
deleteTemplate,
listTemplate,
},
pluralResourceName: "credential-libraries",
parentTypeName: "credential-store",
versionEnabled: true,
createResponseTypes: true,
},
// Host related resources
{
inProto: &hostcatalogs.HostCatalog{},
@@ -357,7 +415,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"host-catalog"},
pluralResourceName: "host-catalogs",
typeOnCreate: true,
versionEnabled: true,
createResponseTypes: true,
@@ -374,7 +432,7 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"host"},
pluralResourceName: "hosts",
parentTypeName: "host-catalog",
versionEnabled: true,
createResponseTypes: true,
@@ -395,8 +453,8 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"host-set"},
parentTypeName: "host-catalog",
pluralResourceName: "host-sets",
parentTypeName: "host-catalog",
sliceSubtypes: map[string]string{
"Hosts": "hostIds",
},
@@ -407,6 +465,14 @@ var inputStructs = []*structInfo{
inProto: &targets.HostSet{},
outFile: "targets/host_set.gen.go",
},
{
inProto: &targets.CredentialLibrary{},
outFile: "targets/credential_library.gen.go",
},
{
inProto: &targets.SessionCredential{},
outFile: "targets/session_credential.gen.go",
},
{
inProto: &targets.SessionAuthorization{},
outFile: "targets/session_authorization.gen.go",
@@ -433,9 +499,10 @@ var inputStructs = []*structInfo{
deleteTemplate,
listTemplate,
},
pathArgs: []string{"target"},
pluralResourceName: "targets",
sliceSubtypes: map[string]string{
"HostSets": "hostSetIds",
"HostSets": "hostSetIds",
"CredentialLibraries": "credentialLibraryIds",
},
extraOptions: []fieldInfo{
{
@@ -478,7 +545,7 @@ var inputStructs = []*structInfo{
readTemplate,
listTemplate,
},
pathArgs: []string{"session"},
pluralResourceName: "sessions",
createResponseTypes: true,
fieldFilter: []string{"private_key"},
recursiveListing: true,
+3
View File
@@ -99,6 +99,7 @@ var (
int32ValueName = (&wrapperspb.Int32Value{}).ProtoReflect().Descriptor().FullName()
structValueName = (&_struct.Struct{}).ProtoReflect().Descriptor().FullName()
timestampName = (&timestamppb.Timestamp{}).ProtoReflect().Descriptor().FullName()
valueName = (&_struct.Value{}).ProtoReflect().Descriptor().FullName()
)
func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) {
@@ -113,6 +114,8 @@ func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) {
return "", "", "int32"
case structValueName:
return "", "", "map[string]interface{}"
case valueName:
return "", "", "interface{}"
case timestampName:
return "", "time", "Time"
default:
+31 -31
View File
@@ -13,23 +13,23 @@ import (
"github.com/iancoleman/strcase"
)
func getArgsAndPaths(in []string, parentTypeName, action string) (colArg, resArg string, colPath, resPath string) {
resArg = fmt.Sprintf("%sId", strcase.ToLowerCamel(strings.ReplaceAll(in[len(in)-1], "-", "_")))
func getArgsAndPaths(pluralResource, parentTypeName, action string) (colArg, colPath, resPath string) {
strToReplace := "scope"
if len(in) == 1 {
if parentTypeName != "" {
strToReplace = parentTypeName
}
} else {
strToReplace = in[len(in)-2]
if parentTypeName != "" {
strToReplace = parentTypeName
}
colArg = fmt.Sprintf("%sId", strcase.ToLowerCamel(strings.ReplaceAll(strToReplace, "-", "_")))
colPath = fmt.Sprintf("%ss", in[len(in)-1])
colPath = pluralResource
// append s at the end only if it isn't already present
if colPath[len(colPath)-1] != 's' {
colPath = fmt.Sprintf("%ss", colPath)
}
if action != "" {
action = fmt.Sprintf(":%s", action)
}
resPath = fmt.Sprintf("fmt.Sprintf(\"%s/%%s%s\", %s)", colPath, action, resArg)
resPath = fmt.Sprintf("fmt.Sprintf(\"%s/%%s%s\", id)", colPath, action)
return
}
@@ -37,7 +37,7 @@ type templateInput struct {
Name string
Package string
Fields []fieldInfo
PathArgs []string
PluralResourceName string
CollectionFunctionArg string
ResourceFunctionArg string
CollectionPath string
@@ -61,7 +61,7 @@ func fillTemplates() {
Name: in.generatedStructure.name,
Package: in.generatedStructure.pkg,
Fields: in.generatedStructure.fields,
PathArgs: in.pathArgs,
PluralResourceName: in.pluralResourceName,
ParentTypeName: in.parentTypeName,
ExtraOptions: in.extraOptions,
VersionEnabled: in.versionEnabled,
@@ -70,8 +70,8 @@ func fillTemplates() {
RecursiveListing: in.recursiveListing,
}
if len(in.pathArgs) > 0 {
input.CollectionFunctionArg, input.ResourceFunctionArg, input.CollectionPath, input.ResourcePath = getArgsAndPaths(in.pathArgs, in.parentTypeName, "")
if len(in.pluralResourceName) > 0 {
input.CollectionFunctionArg, input.CollectionPath, input.ResourcePath = getArgsAndPaths(in.pluralResourceName, in.parentTypeName, "")
}
if err := structTemplate.Execute(outBuf, input); err != nil {
@@ -242,9 +242,9 @@ func (c *Client) List(ctx context.Context, {{ .CollectionFunctionArg }} string,
`))
var readTemplate = template.Must(template.New("").Parse(`
func (c *Client) Read(ctx context.Context, {{ .ResourceFunctionArg }} string, opt... Option) (*{{ .Name }}ReadResult, error) {
if {{ .ResourceFunctionArg }} == "" {
return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Read request")
func (c *Client) Read(ctx context.Context, id string, opt... Option) (*{{ .Name }}ReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -285,9 +285,9 @@ func (c *Client) Read(ctx context.Context, {{ .ResourceFunctionArg }} string, op
`))
var deleteTemplate = template.Must(template.New("").Parse(`
func (c *Client) Delete(ctx context.Context, {{ .ResourceFunctionArg }} string, opt... Option) (*{{ .Name }}DeleteResult, error) {
if {{ .ResourceFunctionArg }} == "" {
return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Delete request")
func (c *Client) Delete(ctx context.Context, id string, opt... Option) (*{{ .Name }}DeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -386,9 +386,9 @@ func (c *Client) Create (ctx context.Context, {{ if .TypeOnCreate }} resourceTyp
`))
var updateTemplate = template.Must(template.New("").Parse(`
func (c *Client) Update(ctx context.Context, {{ .ResourceFunctionArg }} string, version uint32, opt... Option) (*{{ .Name }}UpdateResult, error) {
if {{ .ResourceFunctionArg }} == "" {
return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Update request")
func (c *Client) Update(ctx context.Context, id string, version uint32, opt... Option) (*{{ .Name }}UpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
}
if c.client == nil {
return nil, fmt.Errorf("nil client")
@@ -401,7 +401,7 @@ func (c *Client) Update(ctx context.Context, {{ .ResourceFunctionArg }} string,
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingErr := c.Read(ctx, {{ .ResourceFunctionArg }}, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -465,10 +465,10 @@ var sliceSubtypeTemplate = template.Must(template.New("").Funcs(
{{ range $key, $value := $input.SliceSubtypes }}
{{ $fullName := print $op $key }}
{{ $actionName := kebabCase $fullName }}
{{ $resPath := getPathWithAction $input.PathArgs $input.ParentTypeName $actionName }}
func (c *Client) {{ $fullName }}(ctx context.Context, {{ $input.ResourceFunctionArg }} string, version uint32, {{ $value }} []string, opt... Option) (*{{ $input.Name }}UpdateResult, error) {
if {{ $input.ResourceFunctionArg }} == "" {
return nil, fmt.Errorf("empty {{ $input.ResourceFunctionArg }} value passed into {{ $fullName }} request")
{{ $resPath := getPathWithAction $input.PluralResourceName $input.ParentTypeName $actionName }}
func (c *Client) {{ $fullName }}(ctx context.Context, id string, version uint32, {{ $value }} []string, opt... Option) (*{{ $input.Name }}UpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into {{ $fullName }} request")
}
{{ if ( not ( eq $op "Set" ) ) }}if len({{ $value }}) == 0 {
return nil, errors.New("empty {{ $value }} passed into {{ $fullName }} request")
@@ -484,7 +484,7 @@ func (c *Client) {{ $fullName }}(ctx context.Context, {{ $input.ResourceFunction
if !opts.withAutomaticVersioning {
return nil, errors.New("zero version number passed into {{ $fullName }} request")
}
existingTarget, existingErr := c.Read(ctx, {{ $input.ResourceFunctionArg }}, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
if existingErr != nil {
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
@@ -761,7 +761,7 @@ func kebabCase(in string) string {
return strcase.ToKebab(in)
}
func getPathWithAction(resArgs []string, parentTypeName, action string) string {
_, _, _, resPath := getArgsAndPaths(resArgs, parentTypeName, action)
func getPathWithAction(plResName, parentTypeName, action string) string {
_, _, resPath := getArgsAndPaths(plResName, parentTypeName, action)
return resPath
}
+11 -10
View File
@@ -71,16 +71,17 @@ type Command struct {
FlagRecoveryConfig string
flagOutputCurlString bool
FlagScopeId string
FlagScopeName string
FlagId string
FlagName string
FlagDescription string
FlagAuthMethodId string
FlagHostCatalogId string
FlagVersion int
FlagRecursive bool
FlagFilter string
FlagScopeId string
FlagScopeName string
FlagId string
FlagName string
FlagDescription string
FlagAuthMethodId string
FlagHostCatalogId string
FlagCredentialStoreId string
FlagVersion int
FlagRecursive bool
FlagFilter string
client *api.Client
}
+11 -3
View File
@@ -139,8 +139,10 @@ func WrapMap(prefixSpaces, maxLengthOverride int, input map[string]interface{})
}
// PrintApiError prints the given API error, optionally with context
// information, to the UI in the appropriate format
func (c *Command) PrintApiError(in *api.Error, contextStr string) {
// information, to the UI in the appropriate format. WithAttributeFieldPrefix is
// used, all other options are ignored.
func (c *Command) PrintApiError(in *api.Error, contextStr string, opt ...Option) {
opts := getOpts(opt...)
switch Format(c.UI) {
case "json":
output := struct {
@@ -204,8 +206,14 @@ func (c *Command) PrintApiError(in *api.Error, contextStr string) {
// TODO: Report useful error messages related to "update_mask".
continue
}
var fNameParts []string
if opts.withAttributeFieldPrefix != "" && strings.HasPrefix(field.Name, "attributes.") {
fNameParts = append(fNameParts, opts.withAttributeFieldPrefix)
}
fNameParts = append(fNameParts, strings.ReplaceAll(strings.TrimPrefix(field.Name, "attributes."), "_", "-"))
fName := strings.Join(fNameParts, "-")
output = append(output,
fmt.Sprintf(" Name: -%s", strings.ReplaceAll(strings.TrimPrefix(field.Name, "attributes."), "_", "-")),
fmt.Sprintf(" Name: -%s", fName),
fmt.Sprintf(" Error: %s", field.Description),
)
}
+1 -1
View File
@@ -516,7 +516,7 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error)
if err != nil {
return nil, fmt.Errorf("error creating in memory target: %w", err)
}
tt, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...)
tt, _, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...)
if err != nil {
return nil, fmt.Errorf("error saving target to the db: %w", err)
}
+9
View File
@@ -24,6 +24,7 @@ type Options struct {
withSkipTargetCreation bool
withContainerImage string
withDialect string
withAttributeFieldPrefix string
}
func getDefaultOptions() Options {
@@ -111,3 +112,11 @@ func withDialect(dialect string) Option {
o.withDialect = dialect
}
}
// WithAttributeFieldPrefix tells the command what prefix
// to attach to attribute fields when they are returned as errors.
func WithAttributeFieldPrefix(p string) Option {
return func(o *Options) {
o.withAttributeFieldPrefix = p
}
}
+116
View File
@@ -8,6 +8,8 @@ import (
"github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd"
"github.com/hashicorp/boundary/internal/cmd/commands/config"
"github.com/hashicorp/boundary/internal/cmd/commands/connect"
"github.com/hashicorp/boundary/internal/cmd/commands/credentiallibrariescmd"
"github.com/hashicorp/boundary/internal/cmd/commands/credentialstorescmd"
"github.com/hashicorp/boundary/internal/cmd/commands/database"
"github.com/hashicorp/boundary/internal/cmd/commands/dev"
"github.com/hashicorp/boundary/internal/cmd/commands/groupscmd"
@@ -325,6 +327,102 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
}, nil
},
"credential-libraries": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
}, nil
},
"credential-libraries read": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
Func: "read",
}, nil
},
"credential-libraries delete": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
Func: "delete",
}, nil
},
"credential-libraries list": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
Func: "list",
}, nil
},
"credential-libraries create": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-libraries create vault": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-libraries update": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"credential-libraries update vault": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"credential-stores": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
}, nil
},
"credential-stores read": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
Func: "read",
}, nil
},
"credential-stores delete": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
Func: "delete",
}, nil
},
"credential-stores list": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
Func: "list",
}, nil
},
"credential-stores create": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-stores create vault": func() (cli.Command, error) {
return &credentialstorescmd.VaultCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-stores update": func() (cli.Command, error) {
return &credentialstorescmd.Command{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"credential-stores update vault": func() (cli.Command, error) {
return &credentialstorescmd.VaultCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"groups": func() (cli.Command, error) {
return &groupscmd.Command{
Command: base.NewCommand(ui),
@@ -798,6 +896,24 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "set-host-sets",
}, nil
},
"targets add-credential-libraries": func() (cli.Command, error) {
return &targetscmd.Command{
Command: base.NewCommand(ui),
Func: "add-credential-libraries",
}, nil
},
"targets remove-credential-libraries": func() (cli.Command, error) {
return &targetscmd.Command{
Command: base.NewCommand(ui),
Func: "remove-credential-libraries",
}, nil
},
"targets set-credential-libraries": func() (cli.Command, error) {
return &targetscmd.Command{
Command: base.NewCommand(ui),
Func: "set-credential-libraries",
}, nil
},
"users": func() (cli.Command, error) {
return &userscmd.Command{
@@ -226,7 +226,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -195,7 +195,9 @@ func (c *OidcCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -195,7 +195,9 @@ func (c *PasswordCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -193,7 +193,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -210,7 +210,9 @@ func (c *OidcCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -200,7 +200,9 @@ func (c *PasswordCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -184,7 +184,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -0,0 +1,264 @@
// Code generated by "make cli"; DO NOT EDIT.
package credentiallibrariescmd
import (
"errors"
"fmt"
"sync"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/boundary/sdk/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initFlags() {
flagsOnce.Do(func() {
extraFlags := extraActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsMap[k] = append(flagsMap[k], v...)
}
})
}
var (
_ cli.Command = (*Command)(nil)
_ cli.CommandAutocomplete = (*Command)(nil)
)
type Command struct {
*base.Command
Func string
plural string
}
func (c *Command) AutocompleteArgs() complete.Predictor {
initFlags()
return complete.PredictAnything
}
func (c *Command) AutocompleteFlags() complete.Flags {
initFlags()
return c.Flags().Completions()
}
func (c *Command) Synopsis() string {
if extra := extraSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential library"
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *Command) Help() string {
initFlags()
var helpStr string
helpMap := common.HelpMap("credential library")
switch c.Func {
case "read":
helpStr = helpMap[c.Func]() + c.Flags().Help()
case "delete":
helpStr = helpMap[c.Func]() + c.Flags().Help()
case "list":
helpStr = helpMap[c.Func]() + c.Flags().Help()
default:
helpStr = c.extraHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsMap = map[string][]string{
"read": {"id"},
"delete": {"id"},
"list": {"credential-store-id", "filter"},
}
func (c *Command) Flags() *base.FlagSets {
if len(flagsMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "credential library", flagsMap, c.Func)
extraFlagsFunc(c, set, f)
return set
}
func (c *Command) Run(args []string) int {
initFlags()
switch c.Func {
case "":
return cli.RunResultHelp
case "create", "update":
return cli.RunResultHelp
}
c.plural = "credential library"
switch c.Func {
case "list":
c.plural = "credential librarys"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentiallibraries.Option
if strutil.StrListContains(flagsMap[c.Func], "credential-store-id") {
switch c.Func {
case "list":
if c.FlagCredentialStoreId == "" {
c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID"))
return base.CommandUserError
}
}
}
client, err := c.Client()
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error()))
return base.CommandCliError
}
credentiallibrariesClient := credentiallibraries.NewClient(client)
if c.FlagFilter != "" {
opts = append(opts, credentiallibraries.WithFilter(c.FlagFilter))
}
var version uint32
if ok := extraFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var result api.GenericResult
var listResult api.GenericListResult
switch c.Func {
case "read":
result, err = credentiallibrariesClient.Read(c.Context, c.FlagId, opts...)
case "delete":
result, err = credentiallibrariesClient.Delete(c.Context, c.FlagId, opts...)
case "list":
listResult, err = credentiallibrariesClient.List(c.Context, c.FlagCredentialStoreId, opts...)
}
result, err = executeExtraActions(c, result, err, credentiallibrariesClient, version, opts)
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
output, err := printCustomActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
case "delete":
switch base.Format(c.UI) {
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
case "table":
c.UI.Output("The delete operation completed successfully.")
}
return base.CommandSuccess
case "list":
switch base.Format(c.UI) {
case "json":
if ok := c.PrintJsonItems(listResult); !ok {
return base.CommandCliError
}
case "table":
listedItems := listResult.GetItems().([]*credentiallibraries.CredentialLibrary)
c.UI.Output(c.printListTable(listedItems))
}
return base.CommandSuccess
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(result))
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
var (
flagsOnce = new(sync.Once)
extraActionsFlagsMapFunc = func() map[string][]string { return nil }
extraSynopsisFunc = func(*Command) string { return "" }
extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {}
extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]credentiallibraries.Option) bool { return true }
executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (api.GenericResult, error) {
return inResult, inErr
}
printCustomActionOutput = func(*Command) (bool, error) { return false, nil }
)
@@ -0,0 +1,178 @@
package credentiallibrariescmd
import (
"fmt"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
)
func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
var helpStr string
switch c.Func {
case "":
return base.WrapForHelpText([]string{
"Usage: boundary credential-libraries [sub command] [options] [args]",
"",
" This command allows operations on Boundary credential library resources. Example:",
"",
" Read a credential library:",
"",
` $ boundary credential-libraries read -id clvlt_1234567890`,
"",
" Please see the credential-libraries subcommand help for detailed usage information.",
})
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries create [type] [sub command] [options] [args]",
"",
" This command allows create operations on Boundary credential library resources. Example:",
"",
" Create a credential library:",
"",
` $ boundary credential-libraries create vault -credential-store-id csvlt_1234567890 -vault-path "/some/path"`,
"",
" Please see the typed subcommand help for detailed usage information.",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries update [type] [sub command] [options] [args]",
"",
" This command allows update operations on Boundary credential library resources. Example:",
"",
" Update a vault-type credential library:",
"",
` $ boundary credential-libraries update vault -id clvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
" Please see the typed subcommand help for detailed usage information.",
})
default:
helpStr = helpMap["base"]()
}
return helpStr + c.Flags().Help()
}
func (c *Command) printListTable(items []*credentiallibraries.CredentialLibrary) string {
if len(items) == 0 {
return "No credential library found"
}
var output []string
output = []string{
"",
"Credential Library information:",
}
for i, m := range items {
if i > 0 {
output = append(output, "")
}
if m.Id != "" {
output = append(output,
fmt.Sprintf(" ID: %s", m.Id),
)
} else {
output = append(output,
fmt.Sprintf(" ID: %s", "(not available)"),
)
}
if m.Version > 0 {
output = append(output,
fmt.Sprintf(" Version: %d", m.Version),
)
}
if m.Type != "" {
output = append(output,
fmt.Sprintf(" Type: %s", m.Type),
)
}
if m.Name != "" {
output = append(output,
fmt.Sprintf(" Name: %s", m.Name),
)
}
if m.Description != "" {
output = append(output,
fmt.Sprintf(" Description: %s", m.Description),
)
}
if len(m.AuthorizedActions) > 0 {
output = append(output,
" Authorized Actions:",
base.WrapSlice(6, m.AuthorizedActions),
)
}
}
return base.WrapForHelpText(output)
}
func printItemTable(result api.GenericResult) string {
item := result.GetItem().(*credentiallibraries.CredentialLibrary)
nonAttributeMap := map[string]interface{}{}
if item.Id != "" {
nonAttributeMap["ID"] = item.Id
}
if item.Version != 0 {
nonAttributeMap["Version"] = item.Version
}
if !item.CreatedTime.IsZero() {
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
}
if !item.UpdatedTime.IsZero() {
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
}
if item.CredentialStoreId != "" {
nonAttributeMap["Credential Store ID"] = item.CredentialStoreId
}
if item.Name != "" {
nonAttributeMap["Name"] = item.Name
}
if item.Description != "" {
nonAttributeMap["Description"] = item.Description
}
if item.Type != "" {
nonAttributeMap["Type"] = item.Type
}
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
ret := []string{
"",
"Credential Library information:",
base.WrapMap(2, maxLength+2, nonAttributeMap),
}
if item.Scope != nil {
ret = append(ret,
"",
" Scope:",
base.ScopeInfoForOutput(item.Scope, maxLength),
)
}
if len(item.AuthorizedActions) > 0 {
ret = append(ret,
"",
" Authorized Actions:",
base.WrapSlice(4, item.AuthorizedActions),
)
}
if len(item.Attributes) > 0 {
ret = append(ret,
"",
" Attributes:",
base.WrapMap(4, maxLength, item.Attributes),
)
}
return base.WrapForHelpText(ret)
}
var keySubstMap = map[string]string{
"path": "Path",
"http_method": "HTTP Method",
"http_request_body": "HTTP Request Body",
}
@@ -0,0 +1,243 @@
// Code generated by "make cli"; DO NOT EDIT.
package credentiallibrariescmd
import (
"errors"
"fmt"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/boundary/sdk/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initVaultFlags() {
flagsOnce.Do(func() {
extraFlags := extraVaultActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsVaultMap[k] = append(flagsVaultMap[k], v...)
}
})
}
var (
_ cli.Command = (*VaultCommand)(nil)
_ cli.CommandAutocomplete = (*VaultCommand)(nil)
)
type VaultCommand struct {
*base.Command
Func string
plural string
extraVaultCmdVars
}
func (c *VaultCommand) AutocompleteArgs() complete.Predictor {
initVaultFlags()
return complete.PredictAnything
}
func (c *VaultCommand) AutocompleteFlags() complete.Flags {
initVaultFlags()
return c.Flags().Completions()
}
func (c *VaultCommand) Synopsis() string {
if extra := extraVaultSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential library"
synopsisStr = fmt.Sprintf("%s %s", "vault-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *VaultCommand) Help() string {
initVaultFlags()
var helpStr string
helpMap := common.HelpMap("credential library")
switch c.Func {
default:
helpStr = c.extraVaultHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsVaultMap = map[string][]string{
"create": {"credential-store-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *VaultCommand) Flags() *base.FlagSets {
if len(flagsVaultMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "vault-type credential library", flagsVaultMap, c.Func)
extraVaultFlagsFunc(c, set, f)
return set
}
func (c *VaultCommand) Run(args []string) int {
initVaultFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "vault-type credential library"
switch c.Func {
case "list":
c.plural = "vault-type credential librarys"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsVaultMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentiallibraries.Option
if strutil.StrListContains(flagsVaultMap[c.Func], "credential-store-id") {
switch c.Func {
case "create":
if c.FlagCredentialStoreId == "" {
c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID"))
return base.CommandUserError
}
}
}
client, err := c.Client()
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error()))
return base.CommandCliError
}
credentiallibrariesClient := credentiallibraries.NewClient(client)
switch c.FlagName {
case "":
case "null":
opts = append(opts, credentiallibraries.DefaultName())
default:
opts = append(opts, credentiallibraries.WithName(c.FlagName))
}
switch c.FlagDescription {
case "":
case "null":
opts = append(opts, credentiallibraries.DefaultDescription())
default:
opts = append(opts, credentiallibraries.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, credentiallibraries.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
case "update":
switch c.FlagVersion {
case 0:
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
}
if ok := extraVaultFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var result api.GenericResult
switch c.Func {
case "create":
result, err = credentiallibrariesClient.Create(c.Context, c.FlagCredentialStoreId, opts...)
case "update":
result, err = credentiallibrariesClient.Update(c.Context, c.FlagId, version, opts...)
}
result, err = executeExtraVaultActions(c, result, err, credentiallibrariesClient, version, opts)
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
var opts []base.Option
opts = append(opts, base.WithAttributeFieldPrefix("vault"))
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
output, err := printCustomVaultActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(result))
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
var (
extraVaultActionsFlagsMapFunc = func() map[string][]string { return nil }
extraVaultSynopsisFunc = func(*VaultCommand) string { return "" }
extraVaultFlagsFunc = func(*VaultCommand, *base.FlagSets, *base.FlagSet) {}
extraVaultFlagsHandlingFunc = func(*VaultCommand, *base.FlagSets, *[]credentiallibraries.Option) bool { return true }
executeExtraVaultActions = func(_ *VaultCommand, inResult api.GenericResult, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (api.GenericResult, error) {
return inResult, inErr
}
printCustomVaultActionOutput = func(*VaultCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,117 @@
package credentiallibrariescmd
import (
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/shared-secure-libs/configutil"
)
func init() {
extraVaultFlagsFunc = extraVaultFlagsFuncImpl
extraVaultActionsFlagsMapFunc = extraVaultActionsFlagsMapFuncImpl
extraVaultFlagsHandlingFunc = extraVaultFlagHandlingFuncImpl
}
const (
pathFlagName = "vault-path"
httpMethodFlagName = "vault-http-method"
httpRequestBodyFlagName = "vault-http-request-body"
)
type extraVaultCmdVars struct {
flagPath string
flagHttpMethod string
flagHttpRequestBody string
}
func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
},
}
flags["update"] = flags["create"]
return flags
}
func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Vault Credential Library Options")
for _, name := range flagsVaultMap[c.Func] {
switch name {
case pathFlagName:
f.StringVar(&base.StringVar{
Name: pathFlagName,
Target: &c.flagPath,
Usage: "The path in vault to request credentials from.",
})
case httpMethodFlagName:
f.StringVar(&base.StringVar{
Name: httpMethodFlagName,
Target: &c.flagHttpMethod,
Usage: "The http method the library should use when communicating with vault.",
})
case httpRequestBodyFlagName:
f.StringVar(&base.StringVar{
Name: httpRequestBodyFlagName,
Target: &c.flagHttpRequestBody,
Usage: "The http request body the library uses to communicate with vault. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.",
})
}
}
}
func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]credentiallibraries.Option) bool {
switch c.flagPath {
case "":
default:
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryPath(c.flagPath))
}
switch c.flagHttpMethod {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultVaultCredentialLibraryHttpMethod())
default:
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(c.flagHttpMethod))
}
switch c.flagHttpRequestBody {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultVaultCredentialLibraryHttpRequestBody())
default:
rb, _ := configutil.ParsePath(c.flagHttpRequestBody)
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(rb))
}
return true
}
func (c *VaultCommand) extraVaultHelpFunc(helpMap map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries create vault -credential-store-id [options] [args]",
"",
" Create a vault-type credential library. Example:",
"",
` $ boundary credential-libraries create vault -credential-store-id csvlt_1234567890 -vault-path "/some/path"`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries update vault [options] [args]",
"",
" Update a vault-type credential library given its ID. Example:",
"",
` $ boundary credential-libraries update vault -id clvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
@@ -0,0 +1,269 @@
// Code generated by "make cli"; DO NOT EDIT.
package credentialstorescmd
import (
"errors"
"fmt"
"sync"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/boundary/sdk/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initFlags() {
flagsOnce.Do(func() {
extraFlags := extraActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsMap[k] = append(flagsMap[k], v...)
}
})
}
var (
_ cli.Command = (*Command)(nil)
_ cli.CommandAutocomplete = (*Command)(nil)
)
type Command struct {
*base.Command
Func string
plural string
}
func (c *Command) AutocompleteArgs() complete.Predictor {
initFlags()
return complete.PredictAnything
}
func (c *Command) AutocompleteFlags() complete.Flags {
initFlags()
return c.Flags().Completions()
}
func (c *Command) Synopsis() string {
if extra := extraSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential store"
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *Command) Help() string {
initFlags()
var helpStr string
helpMap := common.HelpMap("credential store")
switch c.Func {
case "read":
helpStr = helpMap[c.Func]() + c.Flags().Help()
case "delete":
helpStr = helpMap[c.Func]() + c.Flags().Help()
case "list":
helpStr = helpMap[c.Func]() + c.Flags().Help()
default:
helpStr = c.extraHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsMap = map[string][]string{
"read": {"id"},
"delete": {"id"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
if len(flagsMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "credential store", flagsMap, c.Func)
extraFlagsFunc(c, set, f)
return set
}
func (c *Command) Run(args []string) int {
initFlags()
switch c.Func {
case "":
return cli.RunResultHelp
case "create", "update":
return cli.RunResultHelp
}
c.plural = "credential store"
switch c.Func {
case "list":
c.plural = "credential stores"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentialstores.Option
if strutil.StrListContains(flagsMap[c.Func], "scope-id") {
switch c.Func {
case "list":
if c.FlagScopeId == "" {
c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID"))
return base.CommandUserError
}
}
}
client, err := c.Client()
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error()))
return base.CommandCliError
}
credentialstoresClient := credentialstores.NewClient(client)
switch c.FlagRecursive {
case true:
opts = append(opts, credentialstores.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, credentialstores.WithFilter(c.FlagFilter))
}
var version uint32
if ok := extraFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var result api.GenericResult
var listResult api.GenericListResult
switch c.Func {
case "read":
result, err = credentialstoresClient.Read(c.Context, c.FlagId, opts...)
case "delete":
result, err = credentialstoresClient.Delete(c.Context, c.FlagId, opts...)
case "list":
listResult, err = credentialstoresClient.List(c.Context, c.FlagScopeId, opts...)
}
result, err = executeExtraActions(c, result, err, credentialstoresClient, version, opts)
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
output, err := printCustomActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
case "delete":
switch base.Format(c.UI) {
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
case "table":
c.UI.Output("The delete operation completed successfully.")
}
return base.CommandSuccess
case "list":
switch base.Format(c.UI) {
case "json":
if ok := c.PrintJsonItems(listResult); !ok {
return base.CommandCliError
}
case "table":
listedItems := listResult.GetItems().([]*credentialstores.CredentialStore)
c.UI.Output(c.printListTable(listedItems))
}
return base.CommandSuccess
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(result))
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
var (
flagsOnce = new(sync.Once)
extraActionsFlagsMapFunc = func() map[string][]string { return nil }
extraSynopsisFunc = func(*Command) string { return "" }
extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {}
extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]credentialstores.Option) bool { return true }
executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *credentialstores.Client, _ uint32, _ []credentialstores.Option) (api.GenericResult, error) {
return inResult, inErr
}
printCustomActionOutput = func(*Command) (bool, error) { return false, nil }
)
@@ -0,0 +1,203 @@
package credentialstorescmd
import (
"fmt"
"sort"
"time"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/cmd/base"
)
func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
var helpStr string
switch c.Func {
case "":
return base.WrapForHelpText([]string{
"Usage: boundary credential-stores [sub command] [options] [args]",
"",
" This command allows operations on Boundary credential store resources. Example:",
"",
" Read a credential store:",
"",
` $ boundary credential-stores read -id csvlt_1234567890`,
"",
" Please see the credential-stores subcommand help for detailed usage information.",
})
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-stores create [type] [sub command] [options] [args]",
"",
" This command allows create operations on Boundary credential store resources. Example:",
"",
" Create a vault-type credential store:",
"",
` $ boundary credential-stores create vault -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`,
"",
" Please see the typed subcommand help for detailed usage information.",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-stores update [type] [sub command] [options] [args]",
"",
" This command allows update operations on Boundary credential store resources. Example:",
"",
" Update a vault-type credential store:",
"",
` $ boundary credential-stores update vault -id csvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
" Please see the typed subcommand help for detailed usage information.",
})
default:
helpStr = helpMap["base"]()
}
return helpStr + c.Flags().Help()
}
func (c *Command) printListTable(items []*credentialstores.CredentialStore) string {
if len(items) == 0 {
return "No credential store found"
}
var output []string
output = []string{
"",
"Credential Store information:",
}
for i, m := range items {
if i > 0 {
output = append(output, "")
}
if m.Id != "" {
output = append(output,
fmt.Sprintf(" ID: %s", m.Id),
)
} else {
output = append(output,
fmt.Sprintf(" ID: %s", "(not available)"),
)
}
if c.FlagRecursive && m.ScopeId != "" {
output = append(output,
fmt.Sprintf(" Scope ID: %s", m.ScopeId),
)
}
if m.Version > 0 {
output = append(output,
fmt.Sprintf(" Version: %d", m.Version),
)
}
if m.Type != "" {
output = append(output,
fmt.Sprintf(" Type: %s", m.Type),
)
}
if m.Name != "" {
output = append(output,
fmt.Sprintf(" Name: %s", m.Name),
)
}
if m.Description != "" {
output = append(output,
fmt.Sprintf(" Description: %s", m.Description),
)
}
if len(m.AuthorizedActions) > 0 {
output = append(output,
" Authorized Actions:",
base.WrapSlice(6, m.AuthorizedActions),
)
}
}
return base.WrapForHelpText(output)
}
func printItemTable(result api.GenericResult) string {
item := result.GetItem().(*credentialstores.CredentialStore)
nonAttributeMap := map[string]interface{}{}
if item.Id != "" {
nonAttributeMap["ID"] = item.Id
}
if item.Version != 0 {
nonAttributeMap["Version"] = item.Version
}
if !item.CreatedTime.IsZero() {
nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123)
}
if !item.UpdatedTime.IsZero() {
nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123)
}
if item.Name != "" {
nonAttributeMap["Name"] = item.Name
}
if item.Description != "" {
nonAttributeMap["Description"] = item.Description
}
if item.Type != "" {
nonAttributeMap["Type"] = item.Type
}
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
ret := []string{
"",
"Credential Store information:",
base.WrapMap(2, maxLength+2, nonAttributeMap),
}
if item.Scope != nil {
ret = append(ret,
"",
" Scope:",
base.ScopeInfoForOutput(item.Scope, maxLength),
)
}
if len(item.AuthorizedActions) > 0 {
ret = append(ret,
"",
" Authorized Actions:",
base.WrapSlice(4, item.AuthorizedActions),
)
}
if len(item.AuthorizedCollectionActions) > 0 {
keys := make([]string, 0, len(item.AuthorizedCollectionActions))
for k := range item.AuthorizedCollectionActions {
keys = append(keys, k)
}
sort.Strings(keys)
ret = append(ret, "",
" Authorized Actions on Credential Store's Collections:",
)
for _, key := range keys {
ret = append(ret,
fmt.Sprintf(" %s:", key),
base.WrapSlice(6, item.AuthorizedCollectionActions[key]),
)
}
}
if len(item.Attributes) > 0 {
ret = append(ret,
"",
" Attributes:",
base.WrapMap(4, maxLength, item.Attributes),
)
}
return base.WrapForHelpText(ret)
}
var keySubstMap = map[string]string{
"address": "Address",
"namespace": "Namespace",
"ca_cert": "CA Cert",
"tls_server_name": "TLS Server Name",
"tls_skip_verify": "Skip TLS Verification",
"token_hmac": "Token HMAC",
"client_certificate": "Client Certificate",
"client_certificate_key_hmac": "Client Certificate Key HMAC",
}
@@ -0,0 +1,248 @@
// Code generated by "make cli"; DO NOT EDIT.
package credentialstorescmd
import (
"errors"
"fmt"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/boundary/sdk/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initVaultFlags() {
flagsOnce.Do(func() {
extraFlags := extraVaultActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsVaultMap[k] = append(flagsVaultMap[k], v...)
}
})
}
var (
_ cli.Command = (*VaultCommand)(nil)
_ cli.CommandAutocomplete = (*VaultCommand)(nil)
)
type VaultCommand struct {
*base.Command
Func string
plural string
extraVaultCmdVars
}
func (c *VaultCommand) AutocompleteArgs() complete.Predictor {
initVaultFlags()
return complete.PredictAnything
}
func (c *VaultCommand) AutocompleteFlags() complete.Flags {
initVaultFlags()
return c.Flags().Completions()
}
func (c *VaultCommand) Synopsis() string {
if extra := extraVaultSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential store"
synopsisStr = fmt.Sprintf("%s %s", "vault-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *VaultCommand) Help() string {
initVaultFlags()
var helpStr string
helpMap := common.HelpMap("credential store")
switch c.Func {
default:
helpStr = c.extraVaultHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsVaultMap = map[string][]string{
"create": {"scope-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *VaultCommand) Flags() *base.FlagSets {
if len(flagsVaultMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "vault-type credential store", flagsVaultMap, c.Func)
extraVaultFlagsFunc(c, set, f)
return set
}
func (c *VaultCommand) Run(args []string) int {
initVaultFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "vault-type credential store"
switch c.Func {
case "list":
c.plural = "vault-type credential stores"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsVaultMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentialstores.Option
if strutil.StrListContains(flagsVaultMap[c.Func], "scope-id") {
switch c.Func {
case "create":
if c.FlagScopeId == "" {
c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID"))
return base.CommandUserError
}
}
}
client, err := c.Client()
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error()))
return base.CommandCliError
}
credentialstoresClient := credentialstores.NewClient(client)
switch c.FlagName {
case "":
case "null":
opts = append(opts, credentialstores.DefaultName())
default:
opts = append(opts, credentialstores.WithName(c.FlagName))
}
switch c.FlagDescription {
case "":
case "null":
opts = append(opts, credentialstores.DefaultDescription())
default:
opts = append(opts, credentialstores.WithDescription(c.FlagDescription))
}
switch c.FlagRecursive {
case true:
opts = append(opts, credentialstores.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, credentialstores.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
case "update":
switch c.FlagVersion {
case 0:
opts = append(opts, credentialstores.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
}
if ok := extraVaultFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var result api.GenericResult
switch c.Func {
case "create":
result, err = credentialstoresClient.Create(c.Context, "vault", c.FlagScopeId, opts...)
case "update":
result, err = credentialstoresClient.Update(c.Context, c.FlagId, version, opts...)
}
result, err = executeExtraVaultActions(c, result, err, credentialstoresClient, version, opts)
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
var opts []base.Option
opts = append(opts, base.WithAttributeFieldPrefix("vault"))
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
output, err := printCustomVaultActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(result))
case "json":
if ok := c.PrintJsonItem(result); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
var (
extraVaultActionsFlagsMapFunc = func() map[string][]string { return nil }
extraVaultSynopsisFunc = func(*VaultCommand) string { return "" }
extraVaultFlagsFunc = func(*VaultCommand, *base.FlagSets, *base.FlagSet) {}
extraVaultFlagsHandlingFunc = func(*VaultCommand, *base.FlagSets, *[]credentialstores.Option) bool { return true }
executeExtraVaultActions = func(_ *VaultCommand, inResult api.GenericResult, inErr error, _ *credentialstores.Client, _ uint32, _ []credentialstores.Option) (api.GenericResult, error) {
return inResult, inErr
}
printCustomVaultActionOutput = func(*VaultCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,186 @@
package credentialstorescmd
import (
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/shared-secure-libs/configutil"
)
func init() {
extraVaultFlagsFunc = extraVaultFlagsFuncImpl
extraVaultActionsFlagsMapFunc = extraVaultActionsFlagsMapFuncImpl
extraVaultFlagsHandlingFunc = extraVaultFlagHandlingFuncImpl
}
const (
addressFlagName = "vault-address"
namespaceFlagName = "vault-namespace"
vaultCaCertFlagName = "vault-ca-cert"
tlsServerNameFlagName = "vault-tls-server-name"
tlsSkipVerifyFlagName = "vault-tls-skip-verify"
vaultTokenFlagName = "vault-token"
clientCertificateFlagName = "vault-client-certificate"
clientCertificateKeyFlagName = "vault-client-certificate-key"
)
type extraVaultCmdVars struct {
flagAddress string
flagNamespace string
flagCaCert string
flagVaultToken string
flagClientCert string
flagClientCertKey string
flagTlsServerName string
flagTlsSkipVerify bool
}
func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
addressFlagName,
namespaceFlagName,
vaultCaCertFlagName,
tlsServerNameFlagName,
tlsSkipVerifyFlagName,
vaultTokenFlagName,
clientCertificateFlagName,
clientCertificateKeyFlagName,
},
}
flags["update"] = flags["create"]
return flags
}
func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Vault Credential Store Options")
for _, name := range flagsVaultMap[c.Func] {
switch name {
case addressFlagName:
f.StringVar(&base.StringVar{
Name: addressFlagName,
Target: &c.flagAddress,
Usage: "The address of the Vault server. This should be a complete URL such as https://127.0.0.1:8200",
})
case namespaceFlagName:
f.StringVar(&base.StringVar{
Name: namespaceFlagName,
Target: &c.flagNamespace,
Usage: "The vault namespace the store should use.",
})
case vaultCaCertFlagName:
f.StringVar(&base.StringVar{
Name: vaultCaCertFlagName,
Target: &c.flagCaCert,
Usage: "The CA Cert to use when connecting to vault. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.",
})
case tlsServerNameFlagName:
f.StringVar(&base.StringVar{
Name: tlsServerNameFlagName,
Target: &c.flagTlsServerName,
Usage: `Name to use as the SNI host when connecting via TLS.`,
})
case tlsSkipVerifyFlagName:
f.BoolVar(&base.BoolVar{
Name: tlsSkipVerifyFlagName,
Target: &c.flagTlsSkipVerify,
Usage: "Whether to skip tls verification.",
})
case vaultTokenFlagName:
f.StringVar(&base.StringVar{
Name: vaultTokenFlagName,
Target: &c.flagVaultToken,
Usage: "The vault token to use when boundary connects to vault for this store.",
})
case clientCertificateFlagName:
f.StringVar(&base.StringVar{
Name: clientCertificateFlagName,
Target: &c.flagClientCert,
Usage: "The client certificate to use when boundary connects to vault for this store. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.",
})
case clientCertificateKeyFlagName:
f.StringVar(&base.StringVar{
Name: clientCertificateKeyFlagName,
Target: &c.flagClientCertKey,
Usage: `The client certificate's private key to use when boundary connects to vault for this store. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.`,
})
}
}
}
func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]credentialstores.Option) bool {
switch c.flagAddress {
case "":
default:
*opts = append(*opts, credentialstores.WithVaultCredentialStoreAddress(c.flagAddress))
}
switch c.flagNamespace {
case "":
case "null":
*opts = append(*opts, credentialstores.DefaultVaultCredentialStoreNamespace())
default:
*opts = append(*opts, credentialstores.WithVaultCredentialStoreNamespace(c.flagNamespace))
}
switch c.flagVaultToken {
case "":
default:
*opts = append(*opts, credentialstores.WithVaultCredentialStoreToken(c.flagVaultToken))
}
switch c.flagCaCert {
case "":
case "null":
*opts = append(*opts, credentialstores.DefaultVaultCredentialStoreCaCert())
default:
cer, _ := configutil.ParsePath(c.flagCaCert)
*opts = append(*opts, credentialstores.WithVaultCredentialStoreCaCert(cer))
}
switch c.flagClientCert {
case "":
case "null":
*opts = append(*opts, credentialstores.DefaultVaultCredentialStoreClientCertificate())
default:
cer, _ := configutil.ParsePath(c.flagClientCert)
*opts = append(*opts, credentialstores.WithVaultCredentialStoreClientCertificate(cer))
}
switch c.flagClientCertKey {
case "":
case "null":
*opts = append(*opts, credentialstores.DefaultVaultCredentialStoreClientCertificateKey())
default:
cer, _ := configutil.ParsePath(c.flagClientCert)
*opts = append(*opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(cer))
}
if c.flagTlsSkipVerify {
*opts = append(*opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(c.flagTlsSkipVerify))
}
return true
}
func (c *VaultCommand) extraVaultHelpFunc(helpMap map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-stores create vault [options] [args]",
"",
" Create a vault-type credential store. Example:",
"",
` $ boundary credential-stores create vault -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-stores update vault [options] [args]",
"",
" Update a vault-type credential store given its ID. Example:",
"",
` $ boundary credential-stores update vault -id csvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
@@ -267,7 +267,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -193,7 +193,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -198,7 +198,9 @@ func (c *StaticCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+3 -1
View File
@@ -204,7 +204,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -195,7 +195,9 @@ func (c *StaticCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -234,7 +234,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -193,7 +193,9 @@ func (c *StaticCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -204,7 +204,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -195,7 +195,9 @@ func (c *OidcCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+3 -1
View File
@@ -291,7 +291,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -241,7 +241,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -191,7 +191,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+183 -8
View File
@@ -1,6 +1,9 @@
package targetscmd
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
@@ -25,17 +28,21 @@ func init() {
}
type extraCmdVars struct {
flagHostSets []string
flagHostId string
sar *targets.SessionAuthorizationResult
flagHostSets []string
flagCredentialLibraries []string
flagHostId string
sar *targets.SessionAuthorizationResult
}
func extraActionsFlagsMapFuncImpl() map[string][]string {
return map[string][]string{
"authorize-session": {"id", "host-id"},
"add-host-sets": {"id", "host-set", "version"},
"remove-host-sets": {"id", "host-set", "version"},
"set-host-sets": {"id", "host-set", "version"},
"authorize-session": {"id", "host-id"},
"add-host-sets": {"id", "host-set", "version"},
"remove-host-sets": {"id", "host-set", "version"},
"set-host-sets": {"id", "host-set", "version"},
"add-credential-libraries": {"id", "credential-library", "version"},
"remove-credential-libraries": {"id", "credential-library", "version"},
"set-credential-libraries": {"id", "credential-library", "version"},
}
}
@@ -53,6 +60,18 @@ func extraSynopsisFuncImpl(c *Command) string {
}
return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth)
case "add-credential-libraries", "set-credential-libraries", "remove-credential-libraries":
var in string
switch {
case strings.HasPrefix(c.Func, "add"):
in = "Add credential library to"
case strings.HasPrefix(c.Func, "set"):
in = "Set the full contents of the credential libraries on"
case strings.HasPrefix(c.Func, "remove"):
in = "Remove credential libraries from"
}
return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth)
case "authorize-session":
return "Request session authorization against the target"
@@ -136,6 +155,42 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
"",
"",
})
case "add-credential-libraries":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary target add-credential-libraries [options] [args]",
"",
" This command allows adding credential-library resources to target resources. Example:",
"",
" Add credential-library resources to a tcp-type target:",
"",
` $ boundary targets add-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`,
"",
"",
})
case "remove-credential-libraries":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary target remove-credential-libraries [options] [args]",
"",
" This command allows removing credential-library resources from target resources. Example:",
"",
" Remove credential-library resources from a tcp-type target:",
"",
` $ boundary targets remove-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`,
"",
"",
})
case "set-credential-libraries":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary target set-credential-libraries [options] [args]",
"",
" This command allows setting the complete set of credential-library resources on a target resource. Example:",
"",
" Set credential-library resources on a tcp-type target:",
"",
` $ boundary targets set-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890`,
"",
"",
})
case "authorize-session":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary target authorize-session [options] [args]",
@@ -171,6 +226,12 @@ func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) {
Target: &c.flagHostId,
Usage: "The ID of a specific host to connect to out of the hosts from the target's host sets. If not specified, one is chosen at random.",
})
case "credential-library":
f.StringSliceVar(&base.StringSliceVar{
Name: "credential-library",
Target: &c.flagCredentialLibraries,
Usage: "The credential-library resource to add, remove, or set. May be specified multiple times.",
})
}
}
@@ -252,6 +313,23 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]targets.Op
}
}
case "add-credential-libraries", "remove-credential-libraries":
if len(c.flagCredentialLibraries) == 0 {
c.UI.Error("No credential-library supplied via -credential-library")
return false
}
case "set-credential-libraries":
switch len(c.flagCredentialLibraries) {
case 0:
c.UI.Error("No credential-libraries supplied via -credential-library")
return false
case 1:
if c.flagCredentialLibraries[0] == "null" {
c.flagCredentialLibraries = nil
}
}
case "authorize-session":
if len(c.flagHostId) != 0 {
*opts = append(*opts, targets.WithHostId(c.flagHostId))
@@ -269,6 +347,12 @@ func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError
return targetClient.RemoveHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...)
case "set-host-sets":
return targetClient.SetHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...)
case "add-credential-libraries":
return targetClient.AddCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
case "remove-credential-libraries":
return targetClient.RemoveCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
case "set-credential-libraries":
return targetClient.SetCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
case "authorize-session":
var err error
c.plural = "a session against target"
@@ -388,6 +472,20 @@ func printItemTable(result api.GenericResult) string {
}
}
var libraryMaps []map[string]interface{}
if len(item.CredentialLibraries) > 0 {
for _, lib := range item.CredentialLibraries {
m := map[string]interface{}{
"ID": lib.Id,
"Credential Store ID": lib.CredentialStoreId,
}
libraryMaps = append(libraryMaps, m)
}
if l := len("Credential Store ID"); l > maxLength {
maxLength = l
}
}
ret := []string{
"",
"Target information:",
@@ -426,6 +524,18 @@ func printItemTable(result api.GenericResult) string {
}
}
if len(item.CredentialLibraries) > 0 {
ret = append(ret,
" Credential Libraries:",
)
for _, m := range libraryMaps {
ret = append(ret,
base.WrapMap(4, maxLength, m),
"",
)
}
}
if len(item.Attributes) > 0 {
ret = append(ret,
" Attributes:",
@@ -466,9 +576,63 @@ func printCustomActionOutputImpl(c *Command) (bool, error) {
ret = append(ret, "", "Target information:")
ret = append(ret,
// We do +2 because there is another +2 offset for host sets below
// We do +2 because there is another +2 offset for credentials below
base.WrapMap(2, maxLength+2, nonAttributeMap),
)
ret = append(ret,
"",
)
if len(item.Credentials) > 0 {
ret = append(ret,
" Credentials:",
)
for _, cred := range item.Credentials {
ret = append(ret,
fmt.Sprintf(" Credential Store ID: %s", cred.CredentialLibrary.CredentialStoreId),
fmt.Sprintf(" Credential Library ID: %s", cred.CredentialLibrary.Id),
fmt.Sprintf(" Credential Library Type: %s", cred.CredentialLibrary.Type))
if len(cred.CredentialLibrary.Name) > 0 {
ret = append(ret,
fmt.Sprintf(" Credential Library Name: %s", cred.CredentialLibrary.Name))
}
if len(cred.CredentialLibrary.Description) > 0 {
ret = append(ret,
fmt.Sprintf(" Credential Library Description: %s", cred.CredentialLibrary.Description))
}
var secretStr string
switch cred.CredentialLibrary.Type {
case "vault":
// If it's Vault, the result will be JSON, except in
// specific circumstances that aren't used for
// credential fetching. So we can take the bytes
// as-is (after base64-decoding), but we'll format
// it nicely.
in, err := base64.StdEncoding.DecodeString(cred.Secret)
if err != nil {
return false, fmt.Errorf("Error decoding secret as base64: %w", err)
}
dst := new(bytes.Buffer)
if err := json.Indent(dst, in, "", " "); err != nil {
return false, fmt.Errorf("Error pretty-printing JSON: %w", err)
}
secretStr = dst.String()
default:
// If it's not Vault, and not another known type,
// print out the base64-encoded value and leave it
// to the user to sort out.
secretStr = fmt.Sprintf(" %s", secretStr)
}
ret = append(ret,
fmt.Sprintf(" Secret:"),
fmt.Sprintf(" %s", secretStr),
"",
)
}
}
c.UI.Output(base.WrapForHelpText(ret))
return true, nil
@@ -511,6 +675,17 @@ func exampleOutput() string {
HostCatalogId: "hcst_1234567890",
},
},
CredentialLibraryIds: []string{"clvlt_1234567890", "clvlt_0987654321"},
CredentialLibraries: []*targets.CredentialLibrary{
{
Id: "clvlt_1234567890",
CredentialStoreId: "csvlt_1234567890",
},
{
Id: "clvlt_098765421",
CredentialStoreId: "clvlt_0987654321",
},
},
Attributes: map[string]interface{}{
"default_port": 22,
},
@@ -213,6 +213,30 @@ func (c *Command) Run(args []string) int {
version = uint32(c.FlagVersion)
}
case "add-credential-libraries":
switch c.FlagVersion {
case 0:
opts = append(opts, targets.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
case "remove-credential-libraries":
switch c.FlagVersion {
case 0:
opts = append(opts, targets.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
case "set-credential-libraries":
switch c.FlagVersion {
case 0:
opts = append(opts, targets.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
}
if ok := extraFlagsHandlingFunc(c, f, &opts); !ok {
@@ -240,7 +264,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
@@ -200,7 +200,9 @@ func (c *TcpCommand) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+3 -1
View File
@@ -267,7 +267,9 @@ func (c *Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+7
View File
@@ -65,6 +65,13 @@ func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string,
Target: &c.FlagHostCatalogId,
Usage: "The host-catalog resource to use for the operation.",
})
case "credential-store-id":
f.StringVar(&base.StringVar{
Name: "credential-store-id",
EnvVar: "BOUNDARY_CREDENTIAL_STORE_ID",
Target: &c.FlagCredentialStoreId,
Usage: "The credential-store resource to use for the operation.",
})
case "recursive":
f.BoolVar(&base.BoolVar{
Name: "recursive",
+60 -1
View File
@@ -62,6 +62,12 @@ type cmdInfo struct {
// NeedsSubtypeInCreate controls whether the sub-type must be passed in as
// an argument to a create call. Targets need this, accounts do not, etc.
NeedsSubtypeInCreate bool
// PrefixAttributeFieldErrorsWithSubactionPrefix will prepend the value in
// SubActionPrefix when reporting errors which are reported in flag format.
// This allows the flags to be defined differently from the the attribute
// names in the API.
PrefixAttributeFieldErrorsWithSubactionPrefix bool
}
var inputStructs = map[string][]*cmdInfo{
@@ -157,6 +163,59 @@ var inputStructs = map[string][]*cmdInfo{
Container: "Scope",
},
},
"credentialstores": {
{
ResourceType: resource.CredentialStore.String(),
Pkg: "credentialstores",
StdActions: []string{"read", "delete", "list"},
IsAbstractType: true,
HasExtraHelpFunc: true,
Container: "Scope",
HasId: true,
},
{
ResourceType: resource.CredentialStore.String(),
Pkg: "credentialstores",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
Container: "Scope",
VersionedActions: []string{"update"},
NeedsSubtypeInCreate: true,
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
},
"credentiallibraries": {
{
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"read", "delete", "list"},
IsAbstractType: true,
HasExtraHelpFunc: true,
Container: "CredentialStore",
HasId: true,
},
{
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
},
"groups": {
{
ResourceType: resource.Group.String(),
@@ -327,7 +386,7 @@ var inputStructs = map[string][]*cmdInfo{
HasName: true,
HasDescription: true,
Container: "Scope",
VersionedActions: []string{"add-host-sets", "remove-host-sets", "set-host-sets"},
VersionedActions: []string{"add-host-sets", "remove-host-sets", "set-host-sets", "add-credential-libraries", "remove-credential-libraries", "set-credential-libraries"},
},
{
ResourceType: resource.Target.String(),
+5 -1
View File
@@ -361,7 +361,11 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
var opts []base.Option
{{ if (and $input.SubActionPrefix $input.PrefixAttributeFieldErrorsWithSubactionPrefix ) }}
opts = append(opts, base.WithAttributeFieldPrefix("{{ $input.SubActionPrefix }}"))
{{ end }}
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
+111
View File
@@ -0,0 +1,111 @@
// Package credential defines interfaces shared by other packages that
// manage credentials for Boundary sessions.
package credential
import (
"context"
"github.com/hashicorp/boundary/internal/db/timestamp"
)
// An Entity is an object distinguished by its identity, rather than its
// attributes. It can contain value objects and other entities.
type Entity interface {
GetPublicId() string
}
// An Aggregate is an entity that is the root of a transactional
// consistency boundary.
type Aggregate interface {
Entity
GetVersion() uint32
GetCreateTime() *timestamp.Timestamp
GetUpdateTime() *timestamp.Timestamp
}
// A Resource is an aggregate with a name and description.
type Resource interface {
Aggregate
GetName() string
GetDescription() string
}
// A Store is a resource that can store, retrieve, and potentially generate
// credentials of differing types and access levels. It belongs to a scope
// and must support the principle of least privilege by providing
// mechanisms to limit the credentials it can access to the minimum
// necessary for the scope it is in.
type Store interface {
Resource
GetScopeId() string
}
// A Library is a resource that provides credentials that are of the same
// type and access level from a single store.
type Library interface {
Resource
GetStoreId() string
}
// Purpose is the purpose of the credential.
type Purpose string
// Credential purpose values.
const (
// ApplicationPurpose is a credential used for application specific
// purposes. Application credentials are returned to the user.
ApplicationPurpose Purpose = "application"
// IngressPurpose is a credential used by a boundary worker to secure
// the connection between the user and the worker. Ingress credentials
// are never returned to the user.
IngressPurpose Purpose = "ingress"
// EgressPurpose is a credential used by a boundary worker to secure
// the connection between the worker and the endpoint. Egress
// credentials are never returned to the user.
EgressPurpose Purpose = "egress"
)
// SecretData represents secret data.
type SecretData interface{}
// Credential is an entity containing secret data.
type Credential interface {
Entity
Secret() SecretData
}
// Dynamic is a credential generated by a library for a specific session.
type Dynamic interface {
Credential
GetSessionId() string
Library() Library
Purpose() Purpose
}
// A Request represents a request for a credential from the SourceId for
// the given purpose. For dynamic credentials, the SourceId is the PublicId
// of a credential library.
type Request struct {
SourceId string
Purpose Purpose
}
// Issuer issues dynamic credentials.
type Issuer interface {
// Issue issues dynamic credentials for a session from the requested
// libraries and for the requested purposes. The sessionId must be a
// valid sessionId. The SourceId in each request must be the public id
// of a library the Issuer can issue credentials from.
//
// If Issue encounters an error, it returns no credentials and revokes
// any credentials issued before encountering the error.
Issue(ctx context.Context, sessionId string, requests []Request) ([]Dynamic, error)
}
// Revoker revokes dynamic credentials.
type Revoker interface {
// Revoke revokes the dynamic credentials issued for sessionid.
Revoke(ctx context.Context, sessionId string) error
}
+459
View File
@@ -0,0 +1,459 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.8
// source: controller/storage/credential/store/v1/credential.proto
// Package store provides protobufs for storing types in the credential package.
package store
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Store struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
ScopeId string `protobuf:"bytes,2,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"`
}
func (x *Store) Reset() {
*x = Store{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Store) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Store) ProtoMessage() {}
func (x *Store) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Store.ProtoReflect.Descriptor instead.
func (*Store) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{0}
}
func (x *Store) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Store) GetScopeId() string {
if x != nil {
return x.ScopeId
}
return ""
}
type Library struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The store_id of the owning store and must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
}
func (x *Library) Reset() {
*x = Library{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Library) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Library) ProtoMessage() {}
func (x *Library) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Library.ProtoReflect.Descriptor instead.
func (*Library) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{1}
}
func (x *Library) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Library) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
type Credential struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
}
func (x *Credential) Reset() {
*x = Credential{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Credential) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Credential) ProtoMessage() {}
func (x *Credential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Credential.ProtoReflect.Descriptor instead.
func (*Credential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{2}
}
func (x *Credential) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
type Static struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The store_id of the owning store and must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
}
func (x *Static) Reset() {
*x = Static{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Static) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Static) ProtoMessage() {}
func (x *Static) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Static.ProtoReflect.Descriptor instead.
func (*Static) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{3}
}
func (x *Static) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Static) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
type Dynamic struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The library_id of the owning library and must be set.
// @inject_tag: `gorm:"not_null"`
LibraryId string `protobuf:"bytes,2,opt,name=library_id,json=libraryId,proto3" json:"library_id,omitempty" gorm:"not_null"`
}
func (x *Dynamic) Reset() {
*x = Dynamic{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Dynamic) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Dynamic) ProtoMessage() {}
func (x *Dynamic) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Dynamic.ProtoReflect.Descriptor instead.
func (*Dynamic) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{4}
}
func (x *Dynamic) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Dynamic) GetLibraryId() string {
if x != nil {
return x.LibraryId
}
return ""
}
var File_controller_storage_credential_store_v1_credential_proto protoreflect.FileDescriptor
var file_controller_storage_credential_store_v1_credential_proto_rawDesc = []byte{
0x0a, 0x37, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x26, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76,
0x31, 0x22, 0x3f, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x49, 0x64, 0x22, 0x41, 0x0a, 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64,
0x22, 0x40, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x49, 0x64, 0x22, 0x45, 0x0a, 0x07, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69,
0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72,
0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_controller_storage_credential_store_v1_credential_proto_rawDescOnce sync.Once
file_controller_storage_credential_store_v1_credential_proto_rawDescData = file_controller_storage_credential_store_v1_credential_proto_rawDesc
)
func file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP() []byte {
file_controller_storage_credential_store_v1_credential_proto_rawDescOnce.Do(func() {
file_controller_storage_credential_store_v1_credential_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_credential_store_v1_credential_proto_rawDescData)
})
return file_controller_storage_credential_store_v1_credential_proto_rawDescData
}
var file_controller_storage_credential_store_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_controller_storage_credential_store_v1_credential_proto_goTypes = []interface{}{
(*Store)(nil), // 0: controller.storage.credential.store.v1.Store
(*Library)(nil), // 1: controller.storage.credential.store.v1.Library
(*Credential)(nil), // 2: controller.storage.credential.store.v1.Credential
(*Static)(nil), // 3: controller.storage.credential.store.v1.Static
(*Dynamic)(nil), // 4: controller.storage.credential.store.v1.Dynamic
}
var file_controller_storage_credential_store_v1_credential_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_controller_storage_credential_store_v1_credential_proto_init() }
func file_controller_storage_credential_store_v1_credential_proto_init() {
if File_controller_storage_credential_store_v1_credential_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_credential_store_v1_credential_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Store); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_credential_store_v1_credential_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Library); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_credential_store_v1_credential_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Credential); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_credential_store_v1_credential_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Static); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_credential_store_v1_credential_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Dynamic); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_credential_store_v1_credential_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_credential_store_v1_credential_proto_goTypes,
DependencyIndexes: file_controller_storage_credential_store_v1_credential_proto_depIdxs,
MessageInfos: file_controller_storage_credential_store_v1_credential_proto_msgTypes,
}.Build()
File_controller_storage_credential_store_v1_credential_proto = out.File
file_controller_storage_credential_store_v1_credential_proto_rawDesc = nil
file_controller_storage_credential_store_v1_credential_proto_goTypes = nil
file_controller_storage_credential_store_v1_credential_proto_depIdxs = nil
}
+69
View File
@@ -0,0 +1,69 @@
package credential
import (
"strings"
"sync"
)
type Subtype int
const (
UnknownSubtype Subtype = iota
VaultSubtype
)
func (t Subtype) String() string {
switch t {
case VaultSubtype:
return "vault"
}
return "unknown"
}
// Subtype uses the provided subtype
func SubtypeFromType(t string) Subtype {
switch {
case strings.EqualFold(strings.TrimSpace(t), VaultSubtype.String()):
return VaultSubtype
}
return UnknownSubtype
}
func SubtypeFromId(id string) Subtype {
i := strings.Index(id, "_")
if i == -1 {
return UnknownSubtype
}
prefix := id[:i]
subtypeMu.RLock()
subtype, ok := subtypes[prefix]
subtypeMu.RUnlock()
if !ok {
return UnknownSubtype
}
return subtype
}
var (
subtypeMu sync.RWMutex
subtypes = make(map[string]Subtype)
)
// Register registers the prefixes for a Subtype. Register panics if the
// subtype is unknown.
func Register(subtype Subtype, prefixes ...string) {
subtypeMu.Lock()
defer subtypeMu.Unlock()
switch subtype {
case VaultSubtype:
default:
panic("credential.Register: subtype is unknown ")
}
for _, prefix := range prefixes {
prefix = strings.TrimSpace(prefix)
subtypes[prefix] = subtype
}
}
+35
View File
@@ -0,0 +1,35 @@
package credential_test
import (
"testing"
"github.com/hashicorp/boundary/internal/credential"
"github.com/stretchr/testify/assert"
)
func TestSubtypeFromId(t *testing.T) {
credential.Register(credential.VaultSubtype, "csvlt")
tests := []struct {
name string
given string
want credential.Subtype
}{
{"empty-string", "", credential.UnknownSubtype},
{"no-prefix-delimiter", "csvlt1234", credential.UnknownSubtype},
{"prefix-first", "_csvlt_1234", credential.UnknownSubtype},
{"unknown-prefix", "kaz_1234", credential.UnknownSubtype},
{"prefix-no-id", "csvlt_", credential.VaultSubtype},
{"vault-prefix", "csvlt_1234", credential.VaultSubtype},
{"prefix-no-delimiter-no-id", "csvlt", credential.UnknownSubtype},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got := credential.SubtypeFromId(tt.given)
assert.Equalf(t, tt.want, got, "given: %s", tt.given)
if got != tt.want {
t.Errorf("(%s): expected %s, actual %s", tt.given, tt.want, got)
}
})
}
}
@@ -0,0 +1,151 @@
package vault
import (
"context"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
"google.golang.org/protobuf/proto"
)
// ClientCertificate contains a client certificate and a private key for
// the certificate. It is owned by a credential store.
type ClientCertificate struct {
*store.ClientCertificate
tableName string `gorm:"-"`
}
// NewClientCertificate creates a new in memory ClientCertificate.
func NewClientCertificate(certificate []byte, key KeySecret) (*ClientCertificate, error) {
const op = "vault.NewClientCertificate"
if len(certificate) == 0 {
return nil, errors.New(errors.InvalidParameter, op, "no certificate")
}
certificateCopy := make([]byte, len(certificate))
copy(certificateCopy, certificate)
var keyCopy KeySecret
if len(key) > 0 {
keyCopy = make(KeySecret, len(key))
copy(keyCopy, key)
}
c := &ClientCertificate{
ClientCertificate: &store.ClientCertificate{
Certificate: certificateCopy,
CertificateKey: keyCopy,
},
}
return c, nil
}
func allocClientCertificate() *ClientCertificate {
return &ClientCertificate{
ClientCertificate: &store.ClientCertificate{},
}
}
func (c *ClientCertificate) clone() *ClientCertificate {
cp := proto.Clone(c.ClientCertificate)
return &ClientCertificate{
ClientCertificate: cp.(*store.ClientCertificate),
}
}
// TableName returns the table name.
func (c *ClientCertificate) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "credential_vault_client_certificate"
}
// SetTableName sets the table name.
func (c *ClientCertificate) SetTableName(n string) {
c.tableName = n
}
func (c *ClientCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(ClientCertificate).encrypt"
if len(c.CertificateKey) == 0 {
errors.New(errors.InvalidParameter, op, "no certificate key defined")
}
if err := structwrapping.WrapStruct(ctx, cipher, c.ClientCertificate, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Encrypt))
}
c.KeyId = cipher.KeyID()
if err := c.hmacCertificateKey(ctx, cipher); err != nil {
errors.Wrap(err, op)
}
return nil
}
func (c *ClientCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(ClientCertificate).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, c.ClientCertificate, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func (c *ClientCertificate) hmacCertificateKey(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(ClientCertificate).hmacCertificateKey"
if cipher == nil {
return errors.New(errors.InvalidParameter, op, "missing cipher")
}
reader, err := kms.NewDerivedReader(cipher, 32, []byte(c.StoreId), nil)
if err != nil {
return errors.Wrap(err, op)
}
key, _, err := ed25519.GenerateKey(reader)
if err != nil {
return errors.New(errors.Encrypt, op, "unable to generate derived key")
}
mac := hmac.New(sha256.New, key)
_, _ = mac.Write(c.CertificateKey)
c.CertificateKeyHmac = mac.Sum(nil)
return nil
}
func (c *ClientCertificate) insertQuery() (query string, queryValues []interface{}) {
query = upsertClientCertQuery
queryValues = []interface{}{
c.StoreId,
c.Certificate,
c.CtCertificateKey,
c.CertificateKeyHmac,
c.KeyId,
}
return
}
func (c *ClientCertificate) deleteQuery() (query string, queryValues []interface{}) {
query = deleteClientCertQuery
queryValues = []interface{}{
c.StoreId,
}
return
}
func (c *ClientCertificate) oplogMessage(opType db.OpType) *oplog.Message {
msg := oplog.Message{
Message: c.clone(),
TypeName: c.TableName(),
}
switch opType {
case db.CreateOp, db.UpdateOp:
msg.OpType = oplog.OpType_OP_TYPE_CREATE
case db.DeleteOp:
msg.OpType = oplog.OpType_OP_TYPE_DELETE
}
return &msg
}
@@ -0,0 +1,125 @@
package vault
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// certPen and keyPem are copied from crypto/tls/example_test.go in the Go
// standard library.
const (
certPem = `-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`
keyPem = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
-----END EC PRIVATE KEY-----`
)
func TestClientCertificate_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
type args struct {
certificate []byte
key []byte
}
tests := []struct {
name string
args args
want *ClientCertificate
wantErr bool
wantEncryptErr bool
}{
{
name: "missing-certificate",
args: args{
key: []byte(keyPem),
},
want: nil,
wantErr: true,
},
{
name: "valid-missing-key",
args: args{
certificate: []byte(certPem),
},
want: &ClientCertificate{
ClientCertificate: &store.ClientCertificate{
Certificate: []byte(certPem),
},
},
wantEncryptErr: true,
},
{
name: "valid",
args: args{
certificate: []byte(certPem),
key: []byte(keyPem),
},
want: &ClientCertificate{
ClientCertificate: &store.ClientCertificate{
Certificate: []byte(certPem),
CertificateKey: []byte(keyPem),
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
require.NotNil(databaseWrapper)
got, err := NewClientCertificate(tt.args.certificate, tt.args.key)
if tt.wantErr {
assert.Error(err)
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
want := tt.want
assert.Empty(got.CtCertificateKey)
assert.Equal(want, got)
err = got.encrypt(ctx, databaseWrapper)
if tt.wantEncryptErr {
require.Error(err)
} else {
require.NoError(err)
require.NoError(got.decrypt(ctx, databaseWrapper))
}
})
}
}
+168
View File
@@ -0,0 +1,168 @@
package vault
import (
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"google.golang.org/protobuf/proto"
)
// A CredentialStatus represents the status of a vault credential.
type CredentialStatus string
const (
// ActiveCredential represents a vault credential that is being used in
// an active session. Credentials in this state are renewed before they
// expire.
ActiveCredential CredentialStatus = "active"
// RevokeCredential represents a vault credential that needs to be
// revoked.
RevokeCredential CredentialStatus = "revoke"
// RevokedCredential represents a credential that has been revoked. This is a
// terminal status. It does not transition to ExpiredCredential.
RevokedCredential CredentialStatus = "revoked"
// ExpiredCredential represents a credential that expired. This is a terminal
// status. It does not transition to RevokedCredential.
ExpiredCredential CredentialStatus = "expired"
)
const (
externalIdSentinel = "\ufffenone"
// UnknownCredentialStatus represents a credential that has an unknown
// status.
UnknownCredentialStatus CredentialStatus = "unknown"
)
// A Credential contains the data for a Vault lease. It is owned by a credential library.
type Credential struct {
*store.Credential
tableName string `gorm:"-"`
expiration time.Duration `gorm:"-"`
}
func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) {
const op = "vault.newCredential"
if libraryId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no library id")
}
if sessionId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no session id")
}
if len(tokenHmac) == 0 {
return nil, errors.New(errors.InvalidParameter, op, "no tokenHmac")
}
status := string(ActiveCredential)
if externalId == "" {
externalId = externalIdSentinel
status = string(UnknownCredentialStatus)
}
l := &Credential{
expiration: expiration.Round(time.Second),
Credential: &store.Credential{
LibraryId: libraryId,
SessionId: sessionId,
ExternalId: externalId,
TokenHmac: tokenHmac,
Status: status,
},
}
return l, nil
}
func allocCredential() *Credential {
return &Credential{
Credential: &store.Credential{},
}
}
func (c *Credential) clone() *Credential {
cp := proto.Clone(c.Credential)
return &Credential{
expiration: c.expiration,
Credential: cp.(*store.Credential),
}
}
// TableName returns the table name.
func (c *Credential) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "credential_vault_credential"
}
// SetTableName sets the table name.
func (c *Credential) SetTableName(n string) {
c.tableName = n
}
func (c *Credential) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{c.PublicId},
"resource-type": []string{"credential-vault-credential"},
"op-type": []string{op.String()},
}
if c.LibraryId != "" {
metadata["library-id"] = []string{c.LibraryId}
}
return metadata
}
func (c *Credential) insertQuery() (query string, queryValues []interface{}) {
queryValues = []interface{}{
c.PublicId,
c.LibraryId,
c.SessionId,
c.TokenHmac,
c.ExternalId,
c.IsRenewable,
c.Status,
"now()",
}
switch {
case c.expiration == 0:
query = insertCredentialWithInfiniteExpirationQuery
default:
query = insertCredentialWithExpirationQuery
queryValues = append(queryValues, int(c.expiration.Round(time.Second).Seconds()))
}
return
}
func (c *Credential) updateSessionQuery(purpose credential.Purpose) (query string, queryValues []interface{}) {
queryValues = []interface{}{
c.PublicId,
c.LibraryId,
c.SessionId,
string(purpose),
}
query = updateSessionCredentialQuery
return
}
func (c *Credential) updateExpirationQuery() (query string, queryValues []interface{}) {
queryValues = []interface{}{
int(c.expiration.Round(time.Second).Seconds()),
c.PublicId,
}
query = updateCredentialExpirationQuery
return
}
func (c *Credential) updateStatusQuery(status CredentialStatus) (query string, queryValues []interface{}) {
queryValues = []interface{}{
status,
c.PublicId,
}
query = updateCredentialStatusQuery
return
}
@@ -0,0 +1,86 @@
package vault
import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/oplog"
"google.golang.org/protobuf/proto"
)
// A Method represents an HTTP method used for communicating with Vault.
type Method string
// HTTP methods use for communicating with Vault.
const (
MethodGet Method = "GET"
MethodPost Method = "POST"
)
// A CredentialLibrary contains a Vault path and is owned by a credential
// store.
type CredentialLibrary struct {
*store.CredentialLibrary
tableName string `gorm:"-"`
}
// NewCredentialLibrary creates a new in memory CredentialLibrary
// for a Vault backend at vaultPath assigned to storeId.
// Name, description, method, and request body are the only valid options.
// All other options are ignored.
func NewCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*CredentialLibrary, error) {
const op = "vault.NewCredentialLibrary"
opts := getOpts(opt...)
l := &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: storeId,
Name: opts.withName,
Description: opts.withDescription,
VaultPath: vaultPath,
HttpRequestBody: opts.withRequestBody,
HttpMethod: string(opts.withMethod),
},
}
return l, nil
}
func allocCredentialLibrary() *CredentialLibrary {
return &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{},
}
}
func (l *CredentialLibrary) clone() *CredentialLibrary {
cp := proto.Clone(l.CredentialLibrary)
return &CredentialLibrary{
CredentialLibrary: cp.(*store.CredentialLibrary),
}
}
// TableName returns the table name.
func (l *CredentialLibrary) TableName() string {
if l.tableName != "" {
return l.tableName
}
return "credential_vault_library"
}
// SetTableName sets the table name.
func (l *CredentialLibrary) SetTableName(n string) {
l.tableName = n
}
func (l *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{l.PublicId},
"resource-type": []string{"credential-vault-library"},
"op-type": []string{op.String()},
}
if l.StoreId != "" {
metadata["store-id"] = []string{l.StoreId}
}
return metadata
}
var _ credential.Library = (*CredentialLibrary)(nil)
@@ -0,0 +1,226 @@
package vault
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMethodType(t *testing.T) {
t.Parallel()
assert := assert.New(t)
assert.IsType(MethodGet, MethodPost)
assert.IsType(MethodPost, MethodGet)
}
func TestCredentialLibrary_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.PublicId, 1)[0]
type args struct {
storeId string
vaultPath string
opts []Option
}
tests := []struct {
name string
args args
want *CredentialLibrary
wantErr bool
wantCreateErr bool
}{
{
name: "missing-store-id",
args: args{
storeId: "",
vaultPath: "vault/path",
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
VaultPath: "vault/path",
},
},
wantCreateErr: true,
},
{
name: "missing-vault-path",
args: args{
storeId: cs.PublicId,
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
},
},
wantCreateErr: true,
},
{
name: "no-options",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
},
},
wantCreateErr: true,
},
{
name: "with-name",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithName("test-name"),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
Name: "test-name",
},
},
wantCreateErr: true,
},
{
name: "with-name-method",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodGet),
WithName("test-name"),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
Name: "test-name",
HttpMethod: string(MethodGet),
},
},
},
{
name: "valid-with-description",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithDescription("test-description"),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
Description: "test-description",
},
},
wantCreateErr: true,
},
{
name: "valid-with-post-method",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodPost),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
HttpMethod: "POST",
VaultPath: "vault/path",
},
},
},
{
name: "valid-with-post-method-and-body",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodPost),
WithRequestBody([]byte("body")),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
HttpMethod: "POST",
VaultPath: "vault/path",
HttpRequestBody: []byte("body"),
},
},
},
{
name: "get-method-with-body",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodGet),
WithRequestBody([]byte("body")),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
HttpRequestBody: []byte("body"),
HttpMethod: "GET",
},
},
wantCreateErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
got, err := NewCredentialLibrary(tt.args.storeId, tt.args.vaultPath, tt.args.opts...)
if tt.wantErr {
assert.Error(err)
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
assert.Equal(tt.want, got)
id, err := newCredentialLibraryId()
assert.NoError(err)
tt.want.PublicId = id
got.PublicId = id
err2 := rw.Create(ctx, got)
if tt.wantCreateErr {
assert.Error(err2)
} else {
assert.NoError(err2)
}
})
}
}
@@ -0,0 +1,196 @@
package vault
import (
"strings"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"google.golang.org/protobuf/proto"
)
// A CredentialStore contains credential libraries. It is owned by a scope.
type CredentialStore struct {
*store.CredentialStore
tableName string `gorm:"-"`
clientCert *ClientCertificate `gorm:"-"`
inputToken TokenSecret `gorm:"-"`
outputToken *Token `gorm:"-"`
privateClientCert *ClientCertificate `gorm:"-"`
privateToken *Token `gorm:"-"`
}
// NewCredentialStore creates a new in memory CredentialStore for a Vault
// server at vaultAddress assigned to scopeId. Name, description, CA cert,
// client cert, namespace, TLS server name, and TLS skip verify are the
// only valid options. All other options are ignored.
func NewCredentialStore(scopeId string, vaultAddress string, token TokenSecret, opt ...Option) (*CredentialStore, error) {
opts := getOpts(opt...)
cs := &CredentialStore{
inputToken: token,
clientCert: opts.withClientCert,
CredentialStore: &store.CredentialStore{
ScopeId: scopeId,
Name: opts.withName,
Description: opts.withDescription,
VaultAddress: vaultAddress,
CaCert: opts.withCACert,
Namespace: opts.withNamespace,
TlsServerName: opts.withTlsServerName,
TlsSkipVerify: opts.withTlsSkipVerify,
},
}
return cs, nil
}
func allocCredentialStore() *CredentialStore {
return &CredentialStore{
CredentialStore: &store.CredentialStore{},
}
}
func (cs *CredentialStore) clone() *CredentialStore {
tokenCopy := make(TokenSecret, len(cs.inputToken))
copy(tokenCopy, cs.inputToken)
var clientCertCopy *ClientCertificate
if cs.clientCert != nil {
clientCertCopy = cs.clientCert.clone()
}
cp := proto.Clone(cs.CredentialStore)
return &CredentialStore{
inputToken: tokenCopy,
clientCert: clientCertCopy,
CredentialStore: cp.(*store.CredentialStore),
}
}
// applyUpdate returns a new CredentialStore with the new values applied to
// this based on the passed in fieldMaskPaths.
func (cs *CredentialStore) applyUpdate(new *CredentialStore, fieldMaskPaths []string) *CredentialStore {
cp := cs.clone()
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
cp.Name = new.Name
case strings.EqualFold(descriptionField, f):
cp.Description = new.Description
case strings.EqualFold(certificateField, f):
if new.clientCert == nil {
cp.clientCert = nil
continue
}
if cp.clientCert == nil {
cp.clientCert = allocClientCertificate()
}
cp.clientCert.Certificate = new.clientCert.GetCertificate()
cp.clientCert.StoreId = cs.GetPublicId()
case strings.EqualFold(certificateKeyField, f):
if new.clientCert == nil {
cp.clientCert = nil
continue
}
if cp.clientCert == nil {
cp.clientCert = allocClientCertificate()
}
cp.clientCert.CertificateKey = new.clientCert.GetCertificateKey()
cp.clientCert.StoreId = cs.GetPublicId()
case strings.EqualFold(vaultAddressField, f):
cp.VaultAddress = new.VaultAddress
case strings.EqualFold(namespaceField, f):
cp.Namespace = new.Namespace
case strings.EqualFold(caCertField, f):
cp.CaCert = new.CaCert
case strings.EqualFold(tlsServerNameField, f):
cp.TlsServerName = new.TlsServerName
case strings.EqualFold(tlsSkipVerifyField, f):
cp.TlsSkipVerify = new.TlsSkipVerify
case strings.EqualFold(tokenField, f):
cp.inputToken = new.inputToken
}
}
return cp
}
// TableName returns the table name.
func (cs *CredentialStore) TableName() string {
if cs.tableName != "" {
return cs.tableName
}
return "credential_vault_store"
}
// SetTableName sets the table name.
func (cs *CredentialStore) SetTableName(n string) {
cs.tableName = n
}
func (cs *CredentialStore) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{cs.PublicId},
"resource-type": []string{"credential-vault-store"},
"op-type": []string{op.String()},
}
if cs.ScopeId != "" {
metadata["scope-id"] = []string{cs.ScopeId}
}
return metadata
}
func (cs *CredentialStore) oplogMessage(opType db.OpType) *oplog.Message {
msg := oplog.Message{
Message: cs.clone(),
TypeName: cs.TableName(),
}
switch opType {
case db.CreateOp:
msg.OpType = oplog.OpType_OP_TYPE_CREATE
case db.UpdateOp:
msg.OpType = oplog.OpType_OP_TYPE_UPDATE
case db.DeleteOp:
msg.OpType = oplog.OpType_OP_TYPE_DELETE
}
return &msg
}
// Token returns the current vault token if available.
func (cs *CredentialStore) Token() *Token {
return cs.outputToken
}
// ClientCertificate returns the client certificate if available.
func (cs *CredentialStore) ClientCertificate() *ClientCertificate {
return cs.clientCert
}
func (cs *CredentialStore) client() (*client, error) {
const op = "vault.(CredentialStore).client"
clientConfig := &clientConfig{
Addr: cs.VaultAddress,
Token: cs.inputToken,
CaCert: cs.CaCert,
TlsServerName: cs.TlsServerName,
TlsSkipVerify: cs.TlsSkipVerify,
Namespace: cs.Namespace,
}
if cs.clientCert != nil {
clientConfig.ClientCert = cs.clientCert.GetCertificate()
clientConfig.ClientKey = cs.clientCert.GetCertificateKey()
}
c, err := newClient(clientConfig)
if err != nil {
return nil, errors.Wrap(err, op)
}
return c, nil
}
func (cs *CredentialStore) softDeleteQuery() (query string, queryValues []interface{}) {
query = softDeleteStoreQuery
queryValues = []interface{}{
cs.PublicId,
}
return
}
@@ -0,0 +1,268 @@
package vault
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestCredentialStore_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
scope := prj
inCert := testClientCert(t, testCaCert(t))
clientCert, err := NewClientCertificate(inCert.Cert.Cert, inCert.Cert.Key)
require.NoError(t, err)
require.NotNil(t, clientCert)
type args struct {
scopeId string
vaultAddress string
token []byte
opts []Option
}
tests := []struct {
name string
args args
want *CredentialStore
wantErr bool
wantCreateErr bool
}{
{
name: "missing-scope-id",
args: args{
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
VaultAddress: "https://vault.consul.service",
},
},
wantCreateErr: true,
},
{
name: "missing-vault-address",
args: args{
scopeId: scope.PublicId,
token: []byte("token"),
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
},
},
wantCreateErr: true,
},
{
name: "missing-vault-token",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
},
want: &CredentialStore{
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
},
},
// The DB does not require there be at least 1 token for a store.
wantCreateErr: false,
},
{
name: "valid-no-options",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
},
},
},
{
name: "valid-with-name",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithName("test-name"),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
Name: "test-name",
},
},
},
{
name: "valid-with-description",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithDescription("test-description"),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
Description: "test-description",
},
},
},
{
name: "valid-with-ca-cert",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithCACert([]byte("ca-cert")),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
CaCert: []byte("ca-cert"),
},
},
},
{
name: "valid-with-namespace",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithNamespace("kazmierczak"),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
Namespace: "kazmierczak",
},
},
},
{
name: "valid-with-tls-server-name",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithTlsServerName("crews"),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
TlsServerName: "crews",
},
},
},
{
name: "valid-with-tls-skip-verify",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithTlsSkipVerify(true),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
TlsSkipVerify: true,
},
},
},
{
name: "valid-with-client-cert",
args: args{
scopeId: scope.PublicId,
vaultAddress: "https://vault.consul.service",
token: []byte("token"),
opts: []Option{
WithClientCert(clientCert),
},
},
want: &CredentialStore{
inputToken: []byte("token"),
clientCert: clientCert,
CredentialStore: &store.CredentialStore{
ScopeId: scope.PublicId,
VaultAddress: "https://vault.consul.service",
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewCredentialStore(tt.args.scopeId, tt.args.vaultAddress, tt.args.token, tt.args.opts...)
if tt.wantErr {
assert.Error(err)
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
assert.Equal(tt.want, got)
assert.Empty(cmp.Diff(tt.want, got.clone(), protocmp.Transform()))
id, err := newCredentialStoreId()
assert.NoError(err)
tt.want.PublicId = id
got.PublicId = id
err2 := rw.Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err2)
} else {
assert.NoError(err2)
}
})
}
}
@@ -0,0 +1,186 @@
package vault
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/iam"
temp "github.com/hashicorp/boundary/internal/session"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestCredential_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.PublicId, 1)[0]
lib := TestCredentialLibraries(t, conn, wrapper, cs.PublicId, 1)[0]
token := cs.Token()
iamRepo := iam.TestRepo(t, conn, wrapper)
session := temp.TestDefaultSession(t, conn, wrapper, iamRepo)
type args struct {
libraryId string
sessionId string
externalId string
tokenHmac []byte
expiration time.Duration
}
tests := []struct {
name string
args args
want *Credential
wantErr bool
}{
{
name: "missing-library-id",
args: args{
sessionId: session.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: token.GetTokenHmac(),
expiration: 5 * time.Minute,
},
want: nil,
wantErr: true,
},
{
name: "missing-session-id",
args: args{
libraryId: lib.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: token.GetTokenHmac(),
expiration: 5 * time.Minute,
},
want: nil,
wantErr: true,
},
{
name: "missing-tokenHmac",
args: args{
libraryId: lib.GetPublicId(),
sessionId: session.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: []byte{},
expiration: 5 * time.Minute,
},
want: nil,
wantErr: true,
},
{
name: "valid",
args: args{
libraryId: lib.GetPublicId(),
sessionId: session.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: token.GetTokenHmac(),
expiration: 5 * time.Minute,
},
want: &Credential{
Credential: &store.Credential{
LibraryId: lib.GetPublicId(),
SessionId: session.GetPublicId(),
ExternalId: "some/vault/credential",
TokenHmac: token.GetTokenHmac(),
Status: string(ActiveCredential),
},
},
},
{
name: "valid-no-external-id",
args: args{
libraryId: lib.GetPublicId(),
sessionId: session.GetPublicId(),
externalId: "",
tokenHmac: token.GetTokenHmac(),
expiration: 5 * time.Minute,
},
want: &Credential{
Credential: &store.Credential{
LibraryId: lib.GetPublicId(),
SessionId: session.GetPublicId(),
ExternalId: "\ufffenone",
TokenHmac: token.GetTokenHmac(),
Status: string(UnknownCredentialStatus),
},
},
},
{
name: "valid-no-expiration",
args: args{
libraryId: lib.GetPublicId(),
sessionId: session.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: token.GetTokenHmac(),
expiration: 0,
},
want: &Credential{
Credential: &store.Credential{
LibraryId: lib.GetPublicId(),
SessionId: session.GetPublicId(),
ExternalId: "some/vault/credential",
TokenHmac: token.GetTokenHmac(),
ExpirationTime: timestamp.New(db.PositiveInfinityTS),
Status: string(ActiveCredential),
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
got, err := newCredential(tt.args.libraryId, tt.args.sessionId,
tt.args.externalId, tt.args.tokenHmac, tt.args.expiration)
if tt.wantErr {
assert.Error(err)
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
id, err := newCredentialId()
assert.NoError(err)
tt.want.PublicId = id
got.PublicId = id
query, queryValues := got.insertQuery()
rows, err2 := rw.Exec(ctx, query, queryValues)
assert.Equal(1, rows)
assert.NoError(err2)
got2 := allocCredential()
got2.PublicId = id
assert.Equal(id, got2.GetPublicId())
require.NoError(rw.LookupById(ctx, got2))
tt.want.LastRenewalTime = got2.LastRenewalTime
tt.want.CreateTime = got2.CreateTime
tt.want.UpdateTime = got2.UpdateTime
tt.want.Version = got2.Version
if tt.want.ExpirationTime == nil {
tt.want.ExpirationTime = got2.ExpirationTime
}
assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform()))
})
}
}
+3
View File
@@ -0,0 +1,3 @@
// Package vault provides access to credentials retrieved from a Vault
// server.
package vault
+18
View File
@@ -0,0 +1,18 @@
package vault
import "testing"
var (
newVaultServer func(t *testing.T, opt ...TestOption) *TestVaultServer = skipNewServer
mountDatabase func(t *testing.T, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase
)
func skipNewServer(t *testing.T, opt ...TestOption) *TestVaultServer {
t.Skip("docker not available")
return nil
}
func skipMountDatabase(t *testing.T, v *TestVaultServer, opt ...TestOption) *TestDatabase {
t.Skip("docker not available")
return nil
}
+20
View File
@@ -0,0 +1,20 @@
package vault
// These constants are the field names used in the vault related field masks.
const (
nameField = "Name"
descriptionField = "Description"
vaultPathField = "VaultPath"
httpMethodField = "HttpMethod"
httpRequestBodyField = "HttpRequestBody"
certificateField = "Certificate"
certificateKeyField = "CertificateKey"
vaultAddressField = "VaultAddress"
namespaceField = "Namespace"
caCertField = "CaCert"
tlsServerNameField = "TlsServerName"
tlsSkipVerifyField = "TlsSkipVerify"
tokenField = "Token"
)
+975
View File
@@ -0,0 +1,975 @@
package vault
import (
"context"
"net/http"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/hashicorp/go-hclog"
vault "github.com/hashicorp/vault/api"
ua "go.uber.org/atomic"
)
const (
tokenRenewalJobName = "vault_token_renewal"
tokenRevocationJobName = "vault_token_revocation"
credentialRenewalJobName = "vault_credential_renewal"
credentialRevocationJobName = "vault_credential_revocation"
credentialStoreCleanupJobName = "vault_credential_store_cleanup"
credentialCleanupJobName = "vault_credential_cleanup"
defaultNextRunIn = 5 * time.Minute
renewalWindow = 10 * time.Minute
)
func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger) error {
const op = "vault.RegisterJobs"
tokenRenewal, err := newTokenRenewalJob(r, w, kms, logger)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, tokenRenewal); err != nil {
return errors.Wrap(err, op, errors.WithMsg("token renewal job"))
}
tokenRevoke, err := newTokenRevocationJob(r, w, kms, logger)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, tokenRevoke); err != nil {
return errors.Wrap(err, op, errors.WithMsg("token revocation job"))
}
credRenewal, err := newCredentialRenewalJob(r, w, kms, logger)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, credRenewal); err != nil {
return errors.Wrap(err, op, errors.WithMsg("credential renewal job"))
}
credRevoke, err := newCredentialRevocationJob(r, w, kms, logger)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, credRevoke); err != nil {
return errors.Wrap(err, op, errors.WithMsg("credential revocation job"))
}
credStoreCleanup, err := newCredentialStoreCleanupJob(r, w, kms, logger)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, credStoreCleanup); err != nil {
return errors.Wrap(err, op, errors.WithMsg("credential store cleanup job"))
}
credCleanup, err := newCredentialCleanupJob(w)
if err != nil {
return errors.Wrap(err, op)
}
if err = scheduler.RegisterJob(ctx, credCleanup); err != nil {
return errors.Wrap(err, op, errors.WithMsg("credential cleanup job"))
}
return nil
}
// TokenRenewalJob is the recurring job that renews credential store Vault tokens that
// are in the `current` and `maintaining` state. The TokenRenewalJob is not thread safe,
// an attempt to Run the job concurrently will result in an JobAlreadyRunning error.
type TokenRenewalJob struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
logger hclog.Logger
limit int
running ua.Bool
numTokens int
numProcessed int
}
// newTokenRenewalJob creates a new in-memory TokenRenewalJob.
//
// WithLimit is the only supported option.
func newTokenRenewalJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*TokenRenewalJob, error) {
const op = "vault.newTokenRenewalJob"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
case logger == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing logger")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &TokenRenewalJob{
reader: r,
writer: w,
kms: kms,
logger: logger,
limit: opts.withLimit,
}, nil
}
// Status returns the current status of the token renewal job. Total is the total number
// of tokens that are set to be renewed. Completed is the number of tokens already renewed.
func (r *TokenRenewalJob) Status() scheduler.JobStatus {
return scheduler.JobStatus{
Completed: r.numProcessed,
Total: r.numTokens,
}
}
// Run queries the vault credential repo for tokens that need to be renewed, it then creates
// a vault client and renews each token. Can not be run in parallel, if Run is invoked while
// already running an error with code JobAlreadyRunning will be returned.
func (r *TokenRenewalJob) Run(ctx context.Context) error {
const op = "vault.(TokenRenewalJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
var ps []*privateStore
// Fetch all tokens that will reach their renewal point within the renewalWindow.
// This is done to avoid constantly scheduling the token renewal job when there are multiple tokens
// set to renew in sequence.
err := r.reader.SearchWhere(ctx, &ps, `token_renewal_time < wt_add_seconds_to_now(?)`, []interface{}{renewalWindow.Seconds()}, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(err, op)
}
// Set numProcessed and numTokens for status report
r.numProcessed, r.numTokens = 0, len(ps)
for _, s := range ps {
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
if err := r.renewToken(ctx, s); err != nil {
r.logger.Error("error renewing token", "credential store id", s.StoreId, "token status", s.TokenStatus, "error", err)
}
r.numProcessed++
}
return nil
}
func (r *TokenRenewalJob) renewToken(ctx context.Context, s *privateStore) error {
const op = "vault.(TokenRenewalJob).renewToken"
databaseWrapper, err := r.kms.GetWrapper(ctx, s.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err = s.decrypt(ctx, databaseWrapper); err != nil {
return errors.Wrap(err, op)
}
token := s.token()
if token == nil {
// Store has no token to renew
return nil
}
vc, err := s.client()
if err != nil {
return errors.Wrap(err, op)
}
var respErr *vault.ResponseError
renewedToken, err := vc.renewToken()
if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusForbidden {
// Vault returned a 403 when attempting a renew self, the token is either expired
// or malformed. Set status to "expired" so credentials created with token can be
// cleaned up.
query, values := token.updateStatusQuery(ExpiredToken)
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "token expired but failed to update repo")
}
if s.TokenStatus == string(CurrentToken) {
r.logger.Info("Vault credential store current token has expired", "credential store id", s.StoreId)
}
// Set credentials associated with this token to expired as Vault will already cascade delete them
_, err = r.writer.Exec(ctx, updateCredentialStatusByTokenQuery, []interface{}{ExpiredCredential, token.TokenHmac})
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("error updating credentials to revoked after revoking token"))
}
return nil
}
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to renew vault token"))
}
tokenExpires, err := renewedToken.TokenTTL()
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration"))
}
token.expiration = tokenExpires
query, values := token.updateExpirationQuery()
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "token renewed but failed to update repo")
}
return nil
}
// NextRunIn queries the vault credential repo to determine when the next token renewal job should run.
func (r *TokenRenewalJob) NextRunIn() (time.Duration, error) {
const op = "vault.(TokenRenewalJob).NextRunIn"
next, err := nextRenewal(r)
if err != nil {
return defaultNextRunIn, errors.Wrap(err, op)
}
return next, nil
}
func nextRenewal(j scheduler.Job) (time.Duration, error) {
const op = "vault.nextRenewal"
var query string
var r db.Reader
switch job := j.(type) {
case *TokenRenewalJob:
query = tokenRenewalNextRunInQuery
r = job.reader
case *CredentialRenewalJob:
query = credentialRenewalNextRunInQuery
r = job.reader
default:
return 0, errors.New(errors.Unknown, op, "unknown job")
}
rows, err := r.Query(context.Background(), query, nil)
if err != nil {
return 0, errors.Wrap(err, op)
}
defer rows.Close()
for rows.Next() {
type NextRenewal struct {
RenewalIn time.Duration
}
var n NextRenewal
err = r.ScanRows(rows, &n)
if err != nil {
return 0, errors.Wrap(err, op)
}
if n.RenewalIn < 0 {
// If we are past the next renewal time, return 0 to schedule immediately
return 0, nil
}
return n.RenewalIn * time.Second, nil
}
return defaultNextRunIn, nil
}
// Name is the unique name of the job.
func (r *TokenRenewalJob) Name() string {
return tokenRenewalJobName
}
// Description is the human readable description of the job.
func (r *TokenRenewalJob) Description() string {
return "Periodically renews Vault credential store tokens that are in a maintaining or current state."
}
// TokenRevocationJob is the recurring job that revokes credential store Vault tokens that
// are in the `maintaining` state and have no credentials being used by an active or pending session.
// The TokenRevocationJob is not thread safe, an attempt to Run the job concurrently will result in
// an JobAlreadyRunning error.
type TokenRevocationJob struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
logger hclog.Logger
limit int
running ua.Bool
numTokens int
numProcessed int
}
// newTokenRevocationJob creates a new in-memory TokenRevocationJob.
//
// WithLimit is the only supported option.
func newTokenRevocationJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*TokenRevocationJob, error) {
const op = "vault.newTokenRevocationJob"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
case logger == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing logger")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &TokenRevocationJob{
reader: r,
writer: w,
kms: kms,
logger: logger,
limit: opts.withLimit,
}, nil
}
// Status returns the current status of the token revocation job. Total is the total number
// of tokens that are set to be revoked. Completed is the number of tokens already revoked.
func (r *TokenRevocationJob) Status() scheduler.JobStatus {
return scheduler.JobStatus{
Completed: r.numProcessed,
Total: r.numTokens,
}
}
// Run queries the vault credential repo for tokens that need to be revoked, it then creates
// a vault client and revokes each token. Can not be run in parallel, if Run is invoked while
// already running an error with code JobAlreadyRunning will be returned.
func (r *TokenRevocationJob) Run(ctx context.Context) error {
const op = "vault.(TokenRevocationJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
// Fetch all tokens in the revoke state as well as all tokens in the maintaining state
// that have no credentials in an active state.
where := `
token_status = 'revoke'
or
(token_status = 'maintaining'
and token_hmac not in (
select token_hmac from credential_vault_credential
where status = 'active'
))
`
var ps []*privateStore
err := r.reader.SearchWhere(ctx, &ps, where, nil, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(err, op)
}
// Set numProcessed and numTokens for s report
r.numProcessed, r.numTokens = 0, len(ps)
for _, s := range ps {
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
if err := r.revokeToken(ctx, s); err != nil {
r.logger.Error("error revoking token", "credential store id", s.StoreId, "error", err)
}
r.numProcessed++
}
return nil
}
func (r *TokenRevocationJob) revokeToken(ctx context.Context, s *privateStore) error {
const op = "vault.(TokenRevocationJob).revokeToken"
databaseWrapper, err := r.kms.GetWrapper(ctx, s.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err = s.decrypt(ctx, databaseWrapper); err != nil {
return errors.Wrap(err, op)
}
token := s.token()
if token == nil {
// Store has no token to revoke
return nil
}
vc, err := s.client()
if err != nil {
return errors.Wrap(err, op)
}
var respErr *vault.ResponseError
err = vc.revokeToken()
if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusForbidden {
// Vault returned a 403 when attempting a revoke self, the token is already expired.
// Clobber error and set status to "revoked" below.
err = nil
}
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to revoke vault token"))
}
query, values := token.updateStatusQuery(RevokedToken)
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "token revoked but failed to update repo")
}
// Set credentials associated with this token to revoked as Vault will already cascade revoke them
_, err = r.writer.Exec(ctx, updateCredentialStatusByTokenQuery, []interface{}{RevokedCredential, token.TokenHmac})
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("error updating credentials to revoked after revoking token"))
}
return nil
}
// NextRunIn determines when the next token revocation job should run.
func (r *TokenRevocationJob) NextRunIn() (time.Duration, error) {
return defaultNextRunIn, nil
}
// Name is the unique name of the job.
func (r *TokenRevocationJob) Name() string {
return tokenRevocationJobName
}
// Description is the human readable description of the job.
func (r *TokenRevocationJob) Description() string {
return "Periodically revokes Vault credential store tokens that are in a maintaining state and have no active credentials associated."
}
// CredentialRenewalJob is the recurring job that renews Vault credentials issued to a session.
// The CredentialRenewalJob is not thread safe, an attempt to Run the job concurrently will result
// in an JobAlreadyRunning error.
type CredentialRenewalJob struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
logger hclog.Logger
limit int
running ua.Bool
numCreds int
numProcessed int
}
// newCredentialRenewalJob creates a new in-memory CredentialRenewalJob.
//
// WithLimit is the only supported option.
func newCredentialRenewalJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialRenewalJob, error) {
const op = "vault.newCredentialRenewalJob"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
case logger == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing logger")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &CredentialRenewalJob{
reader: r,
writer: w,
kms: kms,
logger: logger,
limit: opts.withLimit,
}, nil
}
// Status returns the current status of the credential renewal job. Total is the total number
// of credentials that are set to be renewed. Completed is the number of credential already renewed.
func (r *CredentialRenewalJob) Status() scheduler.JobStatus {
return scheduler.JobStatus{
Completed: r.numProcessed,
Total: r.numCreds,
}
}
// Run queries the vault credential repo for credentials that need to be renewed, it then creates
// a vault client and renews each credential. Can not be run in parallel, if Run is invoked while
// already running an error with code JobAlreadyRunning will be returned.
func (r *CredentialRenewalJob) Run(ctx context.Context) error {
const op = "vault.(CredentialRenewalJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
var creds []*privateCredential
// Fetch all active credentials that will reach their renewal point within the renewalWindow.
// This is done to avoid constantly scheduling the credential renewal job when there are
// multiple credentials set to renew in sequence.
err := r.reader.SearchWhere(ctx, &creds, `renewal_time < wt_add_seconds_to_now(?) and status = ?`, []interface{}{renewalWindow.Seconds(), ActiveCredential}, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(err, op)
}
// Set numProcessed and numTokens for status report
r.numProcessed, r.numCreds = 0, len(creds)
for _, c := range creds {
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
if err := r.renewCred(ctx, c); err != nil {
r.logger.Error("error renewing credential", "credential id", c.PublicId, "error", err)
}
r.numProcessed++
}
return nil
}
func (r *CredentialRenewalJob) renewCred(ctx context.Context, c *privateCredential) error {
const op = "vault.(CredentialRenewalJob).renewCred"
databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err = c.decrypt(ctx, databaseWrapper); err != nil {
return errors.Wrap(err, op)
}
vc, err := c.client()
if err != nil {
return errors.Wrap(err, op)
}
cred := c.toCredential()
var respErr *vault.ResponseError
// Subtract last renewal time from previous expiration time to get lease duration
leaseDuration := c.ExpirationTime.AsTime().Sub(c.LastRenewalTime.AsTime())
renewedCred, err := vc.renewLease(c.ExternalId, leaseDuration)
if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusBadRequest {
// Vault returned a 400 when attempting a renew lease, the lease is either expired
// or the leaseId is malformed. Set status to "expired".
query, values := cred.updateStatusQuery(ExpiredCredential)
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "credential expired but failed to update repo")
}
return nil
}
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to renew credential"))
}
cred.expiration = time.Duration(renewedCred.LeaseDuration) * time.Second
query, values := cred.updateExpirationQuery()
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "credential renewed but failed to update repo")
}
return nil
}
// NextRunIn queries the vault credential repo to determine when the next credential renewal job should run.
func (r *CredentialRenewalJob) NextRunIn() (time.Duration, error) {
const op = "vault.(CredentialRenewalJob).NextRunIn"
next, err := nextRenewal(r)
if err != nil {
return defaultNextRunIn, errors.Wrap(err, op)
}
return next, nil
}
// Name is the unique name of the job.
func (r *CredentialRenewalJob) Name() string {
return credentialRenewalJobName
}
// Description is the human readable description of the job.
func (r *CredentialRenewalJob) Description() string {
return "Periodically renews Vault credentials that are attached to an active/pending session (in the active state)."
}
// CredentialRevocationJob is the recurring job that revokes Vault credentials that are no
// longer being used by an active or pending session.
// The CredentialRevocationJob is not thread safe, an attempt to Run the job concurrently
// will result in an JobAlreadyRunning error.
type CredentialRevocationJob struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
logger hclog.Logger
limit int
running ua.Bool
numCreds int
numProcessed int
}
// newCredentialRevocationJob creates a new in-memory CredentialRevocationJob.
//
// WithLimit is the only supported option.
func newCredentialRevocationJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialRevocationJob, error) {
const op = "vault.newCredentialRevocationJob"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
case logger == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing logger")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &CredentialRevocationJob{
reader: r,
writer: w,
kms: kms,
logger: logger,
limit: opts.withLimit,
}, nil
}
// Status returns the current status of the credential revocation job. Total is the total number
// of credentials that are set to be revoked. Completed is the number of credentials already revoked.
func (r *CredentialRevocationJob) Status() scheduler.JobStatus {
return scheduler.JobStatus{
Completed: r.numProcessed,
Total: r.numCreds,
}
}
// Run queries the vault credential repo for credentials that need to be revoked, it then creates
// a vault client and revokes each credential. Can not be run in parallel, if Run is invoked while
// already running an error with code JobAlreadyRunning will be returned.
func (r *CredentialRevocationJob) Run(ctx context.Context) error {
const op = "vault.(CredentialRevocationJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
var creds []*privateCredential
err := r.reader.SearchWhere(ctx, &creds, "status = ?", []interface{}{RevokeCredential}, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(err, op)
}
// Set numProcessed and numTokens for status report
r.numProcessed, r.numCreds = 0, len(creds)
for _, c := range creds {
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
if err := r.revokeCred(ctx, c); err != nil {
r.logger.Error("error revoking credential", "credential id", c.PublicId, "error", err)
}
r.numProcessed++
}
return nil
}
func (r *CredentialRevocationJob) revokeCred(ctx context.Context, c *privateCredential) error {
const op = "vault.(CredentialRenewalJob).revokeCred"
databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err = c.decrypt(ctx, databaseWrapper); err != nil {
return errors.Wrap(err, op)
}
vc, err := c.client()
if err != nil {
return errors.Wrap(err, op)
}
cred := c.toCredential()
var respErr *vault.ResponseError
err = vc.revokeLease(c.ExternalId)
if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusBadRequest {
// Vault returned a 400 when attempting a revoke lease, the lease is already expired.
// Clobber error and set status to "revoked" below.
err = nil
}
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to revoke credential"))
}
query, values := cred.updateStatusQuery(RevokedCredential)
numRows, err := r.writer.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if numRows != 1 {
return errors.New(errors.Unknown, op, "credential revoked but failed to update repo")
}
return nil
}
// NextRunIn determine when the next credential revocation job should run.
func (r *CredentialRevocationJob) NextRunIn() (time.Duration, error) {
return defaultNextRunIn, nil
}
// Name is the unique name of the job.
func (r *CredentialRevocationJob) Name() string {
return credentialRevocationJobName
}
// Description is the human readable description of the job.
func (r *CredentialRevocationJob) Description() string {
return "Periodically revokes dynamic credentials that are no longer in use and have been set for revocation (in the revoke state)."
}
// CredentialStoreCleanupJob is the recurring job that deletes Vault credential stores that
// have been soft deleted and tokens have been revoked or expired.
// The CredentialStoreCleanupJob is not thread safe, an attempt to Run the job concurrently
// will result in an JobAlreadyRunning error.
type CredentialStoreCleanupJob struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
logger hclog.Logger
limit int
running ua.Bool
numProcessed int
numStores int
}
// newCredentialStoreCleanupJob creates a new in-memory CredentialStoreCleanupJob.
//
// No options are supported.
func newCredentialStoreCleanupJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialStoreCleanupJob, error) {
const op = "vault.newCredentialStoreCleanupJob"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing kms")
case logger == nil:
return nil, errors.New(errors.InvalidParameter, op, "missing logger")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &CredentialStoreCleanupJob{
reader: r,
writer: w,
kms: kms,
logger: logger,
limit: opts.withLimit,
}, nil
}
// Status returns the current status of the credential store cleanup job.
func (r *CredentialStoreCleanupJob) Status() scheduler.JobStatus {
return scheduler.JobStatus{
Completed: r.numProcessed,
Total: r.numStores,
}
}
// Run deletes all vault credential stores in the repo that have been soft deleted.
// Can not be run in parallel, if Run is invoked while already running an error with code
// JobAlreadyRunning will be returned.
func (r *CredentialStoreCleanupJob) Run(ctx context.Context) error {
const op = "vault.(CredentialStoreCleanupJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
// TODO (lcr 06/2021): Oplog does not currently support bulk
// operations. Push cleanup to the database once bulk
// operations are added.
var stores []*CredentialStore
err := r.reader.SearchWhere(ctx, &stores, credStoreCleanupWhereClause, []interface{}{RevokeToken}, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(err, op)
}
// Set numProcessed and numStores for status report
r.numProcessed, r.numStores = 0, len(stores)
for _, store := range stores {
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
oplogWrapper, err := r.kms.GetWrapper(ctx, store.ScopeId, kms.KeyPurposeOplog)
if err != nil {
r.logger.Error("unable to get oplog wrapper for credential store cleanup job", "credential store id", store.PublicId)
r.numProcessed++
continue
}
_, err = r.writer.Delete(ctx, store, db.WithOplog(oplogWrapper, store.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err != nil {
r.logger.Error("error deleting credential store", "error", err, "credential store id", store.PublicId)
}
r.numProcessed++
}
return nil
}
// NextRunIn determine when the next credential store cleanup job should run.
func (r *CredentialStoreCleanupJob) NextRunIn() (time.Duration, error) {
return defaultNextRunIn, nil
}
// Name is the unique name of the job.
func (r *CredentialStoreCleanupJob) Name() string {
return credentialStoreCleanupJobName
}
// Description is the human readable description of the job.
func (r *CredentialStoreCleanupJob) Description() string {
return "Periodically deletes Vault credential stores that have been soft deleted and tokens have been revoked or expired."
}
// CredentialCleanupJob is the recurring job that deletes Vault credentials that are no longer
// attached to a session (have a null session_id) and are not active.
// The CredentialCleanupJob is not thread safe, an attempt to Run the job concurrently
// will result in an JobAlreadyRunning error.
type CredentialCleanupJob struct {
writer db.Writer
running ua.Bool
numCreds int
}
// newCredentialCleanupJob creates a new in-memory CredentialCleanupJob.
//
// No options are supported.
func newCredentialCleanupJob(w db.Writer) (*CredentialCleanupJob, error) {
const op = "vault.newCredentialCleanupJob"
if w == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer")
}
return &CredentialCleanupJob{
writer: w,
}, nil
}
// Status returns the current status of the credential cleanup job.
func (r *CredentialCleanupJob) Status() scheduler.JobStatus {
// Cleanup runs a single exec command to the database, therefore completed and total
// are both set to numCreds.
return scheduler.JobStatus{
Completed: r.numCreds,
Total: r.numCreds,
}
}
// Run deletes all Vault credential in the repo that have a null session_id and are not active.
// Can not be run in parallel, if Run is invoked while already running an error with code
// JobAlreadyRunning will be returned.
func (r *CredentialCleanupJob) Run(ctx context.Context) error {
const op = "vault.(CredentialCleanupJob).Run"
if !r.running.CAS(r.running.Load(), true) {
return errors.New(errors.JobAlreadyRunning, op, "job already running")
}
defer r.running.Store(false)
// Verify context is not done before running
if err := ctx.Err(); err != nil {
return errors.Wrap(err, op)
}
numRows, err := r.writer.Exec(ctx, credCleanupQuery, nil)
if err != nil {
return errors.Wrap(err, op)
}
r.numCreds = numRows
return nil
}
// NextRunIn determine when the next credential cleanup job should run.
func (r *CredentialCleanupJob) NextRunIn() (time.Duration, error) {
return defaultNextRunIn, nil
}
// Name is the unique name of the job.
func (r *CredentialCleanupJob) Name() string {
return credentialCleanupJobName
}
// Description is the human readable description of the job.
func (r *CredentialCleanupJob) Description() string {
return "Periodically deletes Vault credentials that are no longer attached to a session (have a null session_id) and are not active in Vault."
}
File diff suppressed because it is too large. Load diff
+111
View File
@@ -0,0 +1,111 @@
package vault
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how Options are passed as arguments.
type Option func(*options)
// options = how options are represented
type options struct {
withName string
withDescription string
withLimit int
withCACert []byte
withNamespace string
withTlsServerName string
withTlsSkipVerify bool
withClientCert *ClientCertificate
withMethod Method
withRequestBody []byte
}
func getDefaultOptions() options {
return options{}
}
// WithDescription provides an optional description.
func WithDescription(desc string) Option {
return func(o *options) {
o.withDescription = desc
}
}
// WithName provides an optional name.
func WithName(name string) Option {
return func(o *options) {
o.withName = name
}
}
// WithLimit provides an option to provide a limit. Intentionally allowing
// negative integers. If WithLimit < 0, then unlimited results are
// returned. If WithLimit == 0, then default limits are used for results.
func WithLimit(l int) Option {
return func(o *options) {
o.withLimit = l
}
}
// WithCACert provides an optional PEM-encoded certificate
// to verify the Vault server's SSL certificate.
func WithCACert(cert []byte) Option {
return func(o *options) {
o.withCACert = cert
}
}
// WithNamespace provides an optional Vault namespace.
func WithNamespace(namespace string) Option {
return func(o *options) {
o.withNamespace = namespace
}
}
// WithTlsServerName provides an optional name to use as the SNI host when
// connecting to Vault via TLS.
func WithTlsServerName(name string) Option {
return func(o *options) {
o.withTlsServerName = name
}
}
// WithTlsSkipVerify provides an option to disable verification of TLS
// certificates when connection to Vault. Using this option is highly
// discouraged as it decreases the security of data transmissions to and
// from the Vault server.
func WithTlsSkipVerify(skipVerify bool) Option {
return func(o *options) {
o.withTlsSkipVerify = skipVerify
}
}
// WithClientCert provides an optional ClientCertificate to use for TLS
// authentication to a Vault server.
func WithClientCert(clientCert *ClientCertificate) Option {
return func(o *options) {
o.withClientCert = clientCert
}
}
// WithMethod provides an optional Method to use for communicating with
// Vault.
func WithMethod(m Method) Option {
return func(o *options) {
o.withMethod = m
}
}
// WithRequestBody provides an optional request body for sending to Vault
// when requesting credentials using HTTP Post.
func WithRequestBody(b []byte) Option {
return func(o *options) {
o.withRequestBody = b
}
}
+86
View File
@@ -0,0 +1,86 @@
package vault
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("WithName", func(t *testing.T) {
opts := getOpts(WithName("test"))
testOpts := getDefaultOptions()
testOpts.withName = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithDescription", func(t *testing.T) {
opts := getOpts(WithDescription("test desc"))
testOpts := getDefaultOptions()
testOpts.withDescription = "test desc"
assert.Equal(t, opts, testOpts)
})
t.Run("WithLimit", func(t *testing.T) {
opts := getOpts(WithLimit(5))
testOpts := getDefaultOptions()
testOpts.withLimit = 5
assert.Equal(t, opts, testOpts)
})
t.Run("WithCACert", func(t *testing.T) {
opts := getOpts(WithCACert([]byte("test cert")))
testOpts := getDefaultOptions()
testOpts.withCACert = []byte("test cert")
assert.Equal(t, opts, testOpts)
})
t.Run("WithNamespace", func(t *testing.T) {
opts := getOpts(WithNamespace("namespace"))
testOpts := getDefaultOptions()
testOpts.withNamespace = "namespace"
assert.Equal(t, opts, testOpts)
})
t.Run("WithTlsServerName", func(t *testing.T) {
opts := getOpts(WithTlsServerName("server"))
testOpts := getDefaultOptions()
testOpts.withTlsServerName = "server"
assert.Equal(t, opts, testOpts)
})
t.Run("WithTlsSkipVerify", func(t *testing.T) {
opts := getOpts(WithTlsSkipVerify(true))
testOpts := getDefaultOptions()
testOpts.withTlsSkipVerify = true
assert.Equal(t, opts, testOpts)
})
t.Run("WithClientCert", func(t *testing.T) {
testOpts := getDefaultOptions()
assert.Nil(t, testOpts.withClientCert)
inCert := testClientCert(t, testCaCert(t))
cert := inCert.Cert.Cert
key := inCert.Cert.Key
clientCert, err := NewClientCertificate(cert, key)
assert.NoError(t, err)
assert.NotNil(t, clientCert)
opts := getOpts(WithClientCert(clientCert))
require.NotNil(t, opts.withClientCert)
assert.Equal(t, cert, opts.withClientCert.Certificate)
assert.Equal(t, key, opts.withClientCert.CertificateKey)
})
t.Run("WithMethod_Get", func(t *testing.T) {
opts := getOpts(WithMethod(MethodGet))
testOpts := getDefaultOptions()
testOpts.withMethod = MethodGet
assert.Equal(t, opts, testOpts)
})
t.Run("WithMethod_Post", func(t *testing.T) {
opts := getOpts(WithMethod(MethodPost))
testOpts := getDefaultOptions()
testOpts.withMethod = MethodPost
assert.Equal(t, opts, testOpts)
})
t.Run("WithRequestBody", func(t *testing.T) {
opts := getOpts(WithRequestBody([]byte("body")))
testOpts := getDefaultOptions()
testOpts.withRequestBody = []byte("body")
assert.Equal(t, opts, testOpts)
})
}
@@ -0,0 +1,131 @@
package vault
import (
"context"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
"google.golang.org/protobuf/proto"
)
type privateCredential struct {
PublicId string `gorm:"primary_key"`
LibraryId string
SessionId string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ExternalId string
LastRenewalTime *timestamp.Timestamp
ExpirationTime *timestamp.Timestamp
IsRenewable bool
Status string
RenewalTime *timestamp.Timestamp
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenCreateTime *timestamp.Timestamp
TokenUpdateTime *timestamp.Timestamp
TokenLastRenewalTime *timestamp.Timestamp
TokenExpirationTime *timestamp.Timestamp
TokenKeyId string
TokenStatus string
ScopeId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyHmac []byte
ClientKeyId string
}
func (pc *privateCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateCredential).decrypt"
if pc.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: pc.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
pc.Token = ptkv.Token
}
if pc.CtClientKey != nil && pc.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: pc.CtClientKey,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pc.ClientKey = pckv.Key
}
return nil
}
func (pc *privateCredential) client() (*client, error) {
const op = "vault.(privateCredential).client"
clientConfig := &clientConfig{
Addr: pc.VaultAddress,
Token: pc.Token,
CaCert: pc.CaCert,
TlsServerName: pc.TlsServerName,
TlsSkipVerify: pc.TlsSkipVerify,
Namespace: pc.Namespace,
}
if pc.ClientKey != nil {
clientConfig.ClientCert = pc.ClientCert
clientConfig.ClientKey = pc.ClientKey
}
client, err := newClient(clientConfig)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
func (pc *privateCredential) toCredential() *Credential {
return &Credential{
Credential: &store.Credential{
PublicId: pc.PublicId,
LibraryId: pc.LibraryId,
SessionId: pc.SessionId,
TokenHmac: append(pc.TokenHmac[:0:0], pc.TokenHmac...),
CreateTime: proto.Clone(pc.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pc.UpdateTime).(*timestamp.Timestamp),
Version: pc.Version,
ExternalId: pc.ExternalId,
LastRenewalTime: proto.Clone(pc.LastRenewalTime).(*timestamp.Timestamp),
ExpirationTime: proto.Clone(pc.ExpirationTime).(*timestamp.Timestamp),
IsRenewable: pc.IsRenewable,
Status: pc.Status,
},
}
}
// GetPublicId returns the public id.
func (pc *privateCredential) GetPublicId() string {
return pc.PublicId
}
// TableName returns the table name for gorm.
func (pc *privateCredential) TableName() string {
return "credential_vault_credential_private"
}
@@ -0,0 +1,260 @@
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
"google.golang.org/protobuf/proto"
)
var _ credential.Dynamic = (*actualCredential)(nil)
type actualCredential struct {
id string
sessionId string
lib *privateLibrary
secretData map[string]interface{}
purpose credential.Purpose
}
func (ac *actualCredential) GetPublicId() string { return ac.id }
func (ac *actualCredential) GetSessionId() string { return ac.sessionId }
func (ac *actualCredential) Secret() credential.SecretData { return ac.secretData }
func (ac *actualCredential) Library() credential.Library { return ac.lib }
func (ac *actualCredential) Purpose() credential.Purpose { return ac.purpose }
var _ credential.Library = (*privateLibrary)(nil)
type privateLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ScopeId string
VaultPath string
HttpMethod string
HttpRequestBody []byte
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Purpose credential.Purpose `gorm:"-"`
}
func (pl *privateLibrary) clone() *privateLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ScopeId: pl.ScopeId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...),
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: append(pl.CaCert[:0:0], pl.CaCert...),
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...),
Token: append(pl.Token[:0:0], pl.Token...),
CtToken: append(pl.CtToken[:0:0], pl.CtToken...),
TokenKeyId: pl.TokenKeyId,
ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...),
ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...),
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
func (pl *privateLibrary) GetPublicId() string { return pl.PublicId }
func (pl *privateLibrary) GetStoreId() string { return pl.StoreId }
func (pl *privateLibrary) GetName() string { return pl.Name }
func (pl *privateLibrary) GetDescription() string { return pl.Description }
func (pl *privateLibrary) GetVersion() uint32 { return pl.Version }
func (pl *privateLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *privateLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateLibrary).decrypt"
if pl.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: pl.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
pl.Token = ptkv.Token
}
if pl.CtClientKey != nil && pl.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: pl.CtClientKey,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *privateLibrary) client() (*client, error) {
const op = "vault.(privateLibrary).client"
clientConfig := &clientConfig{
Addr: pl.VaultAddress,
Token: pl.Token,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
Namespace: pl.Namespace,
}
if pl.ClientKey != nil {
clientConfig.ClientCert = pl.ClientCert
clientConfig.ClientKey = pl.ClientKey
}
client, err := newClient(clientConfig)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
// TableName returns the table name for gorm.
func (pl *privateLibrary) TableName() string {
return "credential_vault_library_private"
}
func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credential.Request) ([]*privateLibrary, error) {
const op = "vault.(Repository).getPrivateLibraries"
mapper, err := newMapper(requests)
if err != nil {
return nil, errors.Wrap(err, op)
}
libIds := mapper.libIds()
inClause := strings.TrimSuffix(strings.Repeat("?,", len(libIds)), ",")
query := fmt.Sprintf(selectPrivateLibrariesQuery, inClause)
var params []interface{}
for _, v := range libIds {
params = append(params, v)
}
rows, err := r.reader.Query(ctx, query, params)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("query failed"))
}
defer rows.Close()
var libs []*privateLibrary
for rows.Next() {
var lib privateLibrary
if err := r.reader.ScanRows(rows, &lib); err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed"))
}
purps := mapper.get(lib.GetPublicId())
if len(purps) == 0 {
return nil, errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("unknown library"))
}
for _, purp := range purps {
cp := lib.clone()
cp.Purpose = purp
libs = append(libs, cp)
}
}
for _, pl := range libs {
databaseWrapper, err := r.kms.GetWrapper(ctx, pl.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := pl.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
}
}
return libs, nil
}
// requestMap takes a slice of credential requests and provides of list of
// library IDs with duplicate IDs removed. It also provides a way to lookup
// the list of credential purposes for a particular library ID.
//
// A single library can be used to retrieve multiple credentials as long as
// each credential is for a different purpose. When retrieving the private
// libraries from the database, a list of library IDs with duplicates
// removed is needed. When requesting credentials from vault, any library
// being used for multiple purposes needs to be duplicated with the purpose
// so multiple requests are made to vault using the same library.
type requestMap struct {
ids map[string][]credential.Purpose
}
func newMapper(requests []credential.Request) (*requestMap, error) {
ids := make(map[string][]credential.Purpose, len(requests))
for _, req := range requests {
if purps, ok := ids[req.SourceId]; ok {
for _, purp := range purps {
if purp == req.Purpose {
return nil, errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("duplicate library and purpose"))
}
}
}
ids[req.SourceId] = append(ids[req.SourceId], req.Purpose)
}
return &requestMap{
ids: ids,
}, nil
}
func (m *requestMap) libIds() []string {
var ids []string
for id := range m.ids {
ids = append(ids, id)
}
return ids
}
func (m *requestMap) get(libraryId string) []credential.Purpose {
return m.ids[libraryId]
}
@@ -0,0 +1,227 @@
package vault
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepository_getPrivateLibraries(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
tests := []struct {
name string
tls TestVaultTLS
}{
{
name: "no-tls-valid-token",
},
{
name: "server-tls-valid-token",
tls: TestServerTLS,
},
{
name: "client-tls-valid-token",
tls: TestClientTLS,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls))
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
require.NoError(err)
require.NotNil(repo)
var opts []Option
if tt.tls == TestServerTLS {
opts = append(opts, WithCACert(v.CaCert))
}
if tt.tls == TestClientTLS {
opts = append(opts, WithCACert(v.CaCert))
clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey)
require.NoError(err)
opts = append(opts, WithClientCert(clientCert))
}
_, token := v.CreateToken(t)
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...)
assert.NoError(err)
require.NotNil(credStoreIn)
origStore, err := repo.CreateCredentialStore(ctx, credStoreIn)
assert.NoError(err)
require.NotNil(origStore)
origLookup, err := repo.LookupCredentialStore(ctx, origStore.GetPublicId())
assert.NoError(err)
require.NotNil(origLookup)
assert.NotNil(origLookup.Token())
assert.Equal(origStore.GetPublicId(), origLookup.GetPublicId())
libs := make(map[string]*CredentialLibrary, 3)
var requests []credential.Request
{
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path")
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose}
requests = append(requests, req)
}
{
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", WithMethod(MethodPost))
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose}
requests = append(requests, req)
}
{
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", WithMethod(MethodPost), WithRequestBody([]byte(`{"common_name":"boundary.com"}`)))
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose}
requests = append(requests, req)
}
gotLibs, err := repo.getPrivateLibraries(ctx, requests)
assert.NoError(err)
require.NotNil(gotLibs)
assert.Len(gotLibs, len(libs))
for _, got := range gotLibs {
assert.Equal(TokenSecret(token), got.Token)
if tt.tls == TestClientTLS {
require.NotNil(got.ClientKey)
assert.Equal(v.ClientKey, []byte(got.ClientKey))
}
want, ok := libs[got.PublicId]
require.True(ok)
assert.Equal(prj.GetPublicId(), got.ScopeId)
assert.Equal(want.StoreId, got.StoreId)
assert.Equal(want.VaultPath, got.VaultPath)
assert.Equal(want.HttpMethod, got.HttpMethod)
assert.Equal(want.HttpRequestBody, got.HttpRequestBody)
}
})
}
}
func TestRequestMap(t *testing.T) {
type args struct {
requests []credential.Request
}
tests := []struct {
name string
args args
wantLibIds []string
wantErr bool
}{
{
name: "empty",
},
{
name: "one",
args: args{
requests: []credential.Request{
{
SourceId: "kaz",
Purpose: credential.ApplicationPurpose,
},
},
},
wantLibIds: []string{"kaz"},
},
{
name: "two-libs",
args: args{
requests: []credential.Request{
{
SourceId: "kaz",
Purpose: credential.ApplicationPurpose,
},
{
SourceId: "gary",
Purpose: credential.EgressPurpose,
},
},
},
wantLibIds: []string{"kaz", "gary"},
},
{
name: "one-lib-two-purps",
args: args{
requests: []credential.Request{
{
SourceId: "kaz",
Purpose: credential.ApplicationPurpose,
},
{
SourceId: "kaz",
Purpose: credential.EgressPurpose,
},
},
},
wantLibIds: []string{"kaz"},
},
{
name: "one-lib-dup-purps-error",
args: args{
requests: []credential.Request{
{
SourceId: "kaz",
Purpose: credential.ApplicationPurpose,
},
{
SourceId: "kaz",
Purpose: credential.ApplicationPurpose,
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
mapper, err := newMapper(tt.args.requests)
if tt.wantErr {
assert.Error(err)
assert.Nil(mapper)
return
}
assert.NoError(err)
require.NotNil(mapper)
assert.ElementsMatch(tt.wantLibIds, mapper.libIds())
})
}
}
+212
View File
@@ -0,0 +1,212 @@
package vault
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
)
func (r *Repository) listRevokePrivateStores(ctx context.Context, opt ...Option) ([]*privateStore, error) {
const op = "vault.(Repository).listRevokePrivateStores"
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
limit = opts.withLimit
}
var stores []*privateStore
where, values := "token_status = ?", []interface{}{"revoke"}
if err := r.reader.SearchWhere(ctx, &stores, where, values, db.WithLimit(limit)); err != nil {
return nil, errors.Wrap(err, op)
}
for _, store := range stores {
databaseWrapper, err := r.kms.GetWrapper(ctx, store.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := store.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
}
}
return stores, nil
}
func (r *Repository) lookupPrivateStore(ctx context.Context, publicId string) (*privateStore, error) {
const op = "vault.(Repository).lookupPrivateStore"
if publicId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no public id")
}
ps := allocPrivateStore()
if err := r.reader.LookupWhere(ctx, &ps, "public_id = ? and token_status = ?", publicId, CurrentToken); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, ps.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := ps.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
}
return ps, nil
}
type privateStore struct {
PublicId string `gorm:"primary_key"`
ScopeId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
DeleteTime *timestamp.Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
StoreId string
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenCreateTime *timestamp.Timestamp
TokenUpdateTime *timestamp.Timestamp
TokenLastRenewalTime *timestamp.Timestamp
TokenExpirationTime *timestamp.Timestamp
TokenRenewalTime *timestamp.Timestamp
TokenKeyId string
TokenStatus string
ClientCert []byte
ClientKeyId string
ClientKey KeySecret
CtClientKey []byte
ClientCertKeyHmac []byte
}
func allocPrivateStore() *privateStore {
return &privateStore{}
}
func (ps *privateStore) toCredentialStore() *CredentialStore {
cs := allocCredentialStore()
cs.PublicId = ps.PublicId
cs.ScopeId = ps.ScopeId
cs.Name = ps.Name
cs.Description = ps.Description
cs.CreateTime = ps.CreateTime
cs.UpdateTime = ps.UpdateTime
cs.DeleteTime = ps.DeleteTime
cs.Version = ps.Version
cs.VaultAddress = ps.VaultAddress
cs.Namespace = ps.Namespace
cs.CaCert = ps.CaCert
cs.TlsServerName = ps.TlsServerName
cs.TlsSkipVerify = ps.TlsSkipVerify
cs.privateToken = ps.token()
if ps.ClientCert != nil {
cert := allocClientCertificate()
cert.StoreId = ps.StoreId
cert.Certificate = ps.ClientCert
cert.CtCertificateKey = ps.CtClientKey
cert.CertificateKeyHmac = ps.ClientCertKeyHmac
cert.KeyId = ps.ClientKeyId
cs.privateClientCert = cert
}
return cs
}
func (ps *privateStore) token() *Token {
if ps.TokenHmac != nil {
tk := allocToken()
tk.StoreId = ps.StoreId
tk.TokenHmac = ps.TokenHmac
tk.LastRenewalTime = ps.TokenLastRenewalTime
tk.ExpirationTime = ps.TokenExpirationTime
tk.CreateTime = ps.TokenCreateTime
tk.UpdateTime = ps.TokenUpdateTime
tk.CtToken = ps.CtToken
tk.KeyId = ps.TokenKeyId
tk.Status = ps.TokenStatus
return tk
}
return nil
}
func (ps *privateStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateStore).decrypt"
if ps.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: ps.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
ps.Token = ptkv.Token
}
if ps.CtClientKey != nil && ps.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: ps.CtClientKey,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
ps.ClientKey = pckv.Key
}
return nil
}
func (ps *privateStore) client() (*client, error) {
const op = "vault.(privateStore).client"
clientConfig := &clientConfig{
Addr: ps.VaultAddress,
Token: ps.Token,
CaCert: ps.CaCert,
TlsServerName: ps.TlsServerName,
TlsSkipVerify: ps.TlsSkipVerify,
Namespace: ps.Namespace,
}
if ps.ClientKey != nil {
clientConfig.ClientCert = ps.ClientCert
clientConfig.ClientKey = ps.ClientKey
}
client, err := newClient(clientConfig)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
// GetPublicId returns the public id.
func (ps *privateStore) GetPublicId() string { return ps.PublicId }
// TableName returns the table name for gorm.
func (ps *privateStore) TableName() string {
return "credential_vault_store_private"
}
@@ -0,0 +1,90 @@
package vault
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepository_lookupPrivateStore(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
tests := []struct {
name string
tls TestVaultTLS
}{
{
name: "no-tls-valid-token",
},
{
name: "server-tls-valid-token",
tls: TestServerTLS,
},
{
name: "client-tls-valid-token",
tls: TestClientTLS,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
require.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls))
var opts []Option
if tt.tls == TestServerTLS {
opts = append(opts, WithCACert(v.CaCert))
}
if tt.tls == TestClientTLS {
opts = append(opts, WithCACert(v.CaCert))
clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey)
require.NoError(err)
opts = append(opts, WithClientCert(clientCert))
}
_, token := v.CreateToken(t)
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...)
assert.NoError(err)
require.NotNil(credStoreIn)
orig, err := repo.CreateCredentialStore(ctx, credStoreIn)
assert.NoError(err)
require.NotNil(orig)
origLookup, err := repo.LookupCredentialStore(ctx, orig.GetPublicId())
assert.NoError(err)
require.NotNil(origLookup)
assert.NotNil(origLookup.Token())
assert.Equal(orig.GetPublicId(), origLookup.GetPublicId())
got, err := repo.lookupPrivateStore(ctx, orig.GetPublicId())
assert.NoError(err)
require.NotNil(got)
assert.Equal(orig.GetPublicId(), got.GetPublicId())
assert.Equal(token, string(got.Token))
if tt.tls == TestClientTLS {
require.NotNil(got.ClientKey)
assert.Equal(v.ClientKey, []byte(got.ClientKey))
}
})
}
}
+42
View File
@@ -0,0 +1,42 @@
package vault
import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
// PublicId prefixes for the resources in the vault package.
const (
CredentialStorePrefix = "csvlt"
CredentialLibraryPrefix = "clvlt"
DynamicCredentialPrefix = "cdvlt"
)
func newCredentialStoreId() (string, error) {
id, err := db.NewPublicId(CredentialStorePrefix)
if err != nil {
return "", errors.Wrap(err, "vault.newCredentialStoreId")
}
return id, nil
}
func newCredentialId() (string, error) {
id, err := db.NewPublicId(DynamicCredentialPrefix)
if err != nil {
return "", errors.Wrap(err, "vault.newCredentialId")
}
return id, nil
}
func newCredentialLibraryId() (string, error) {
id, err := db.NewPublicId(CredentialLibraryPrefix)
if err != nil {
return "", errors.Wrap(err, "vault.newCredentialLibraryId")
}
return id, nil
}
func init() {
credential.Register(credential.VaultSubtype, CredentialStorePrefix, CredentialLibraryPrefix, DynamicCredentialPrefix)
}
+189
View File
@@ -0,0 +1,189 @@
package vault
const (
insertTokenQuery = `
insert into credential_vault_token (
token_hmac, -- $1
token, -- $2
store_id, -- $3
key_id, -- $4
status, -- $5
last_renewal_time, -- $6
expiration_time -- $7
) values (
$1, -- token_hmac
$2, -- token
$3, -- store_id
$4, -- key_id
$5, -- status
$6, -- last_renewal_time
wt_add_seconds_to_now($7) -- expiration_time
);
`
insertCredentialWithExpirationQuery = `
insert into credential_vault_credential (
public_id, -- $1
library_id, -- $2
session_id, -- $3
token_hmac, -- $4
external_id, -- $5
is_renewable, -- $6
status, -- $7
last_renewal_time, -- $8
expiration_time -- $9
) values (
$1, -- public_id
$2, -- library_id
$3, -- session_id
$4, -- token_hmac
$5, -- external_id
$6, -- is_renewable
$7, -- status
$8, -- last_renewal_time
wt_add_seconds_to_now($9) -- expiration_time
);
`
insertCredentialWithInfiniteExpirationQuery = `
insert into credential_vault_credential (
public_id, -- $1
library_id, -- $2
session_id, -- $3
token_hmac, -- $4
external_id, -- $5
is_renewable, -- $6
status, -- $7
last_renewal_time, -- $8
expiration_time -- infinity
) values (
$1, -- public_id
$2, -- library_id
$3, -- session_id
$4, -- token_hmac
$5, -- external_id
$6, -- is_renewable
$7, -- status
$8, -- last_renewal_time
'infinity' -- expiration_time
);
`
upsertClientCertQuery = `
insert into credential_vault_client_certificate
(store_id, certificate, certificate_key, certificate_key_hmac, key_id)
values
($1, $2, $3, $4, $5)
on conflict (store_id) do update
set certificate = excluded.certificate,
certificate_key = excluded.certificate_key,
certificate_key_hmac = excluded.certificate_key_hmac,
key_id = excluded.key_id
returning *;
`
deleteClientCertQuery = `
delete from credential_vault_client_certificate
where store_id = $1;
`
selectPrivateLibrariesQuery = `
select *
from credential_vault_library_private
where public_id in (%s);
`
updateSessionCredentialQuery = `
update session_credential_dynamic
set credential_id = $1
where library_id = $2
and session_id = $3
and credential_purpose = $4
and credential_id is null
returning *;
`
updateTokenExpirationQuery = `
update credential_vault_token
set last_renewal_time = now(),
expiration_time = wt_add_seconds_to_now(?)
where token_hmac = ?;
`
updateTokenStatusQuery = `
update credential_vault_token
set status = ?
where token_hmac = ?;
`
tokenRenewalNextRunInQuery = `
select extract(epoch from (last_renewal_time + (expiration_time - last_renewal_time) / 2) - now())::int as renewal_in
from credential_vault_token
where expiration_time = (
select min(expiration_time)
from credential_vault_token
where status in ('current', 'maintaining')
);
`
revokeCredentialsQuery = `
update credential_vault_credential
set status = 'revoke'
where session_id = $1
and status = 'active';
`
updateCredentialStatusByTokenQuery = `
update credential_vault_credential
set status = ?
where token_hmac = ?;
`
credentialRenewalNextRunInQuery = `
select
extract(epoch from (renewal_time - now()))::int as renewal_in
from credential_vault_credential_private
where expiration_time = (
select min(expiration_time)
from credential_vault_credential_private
where status = 'active'
);
`
updateCredentialExpirationQuery = `
update credential_vault_credential
set last_renewal_time = now(),
expiration_time = wt_add_seconds_to_now(?)
where public_id = ?;
`
updateCredentialStatusQuery = `
update credential_vault_credential
set status = ?
where public_id = ?;
`
softDeleteStoreQuery = `
update credential_vault_store
set delete_time = now()
where public_id = $1
and delete_time is null
returning *;
`
credStoreCleanupWhereClause = `
delete_time is not null
and public_id not in
(
select store_id from credential_vault_token
where status = ?
and expiration_time > now()
)
`
credCleanupQuery = `
delete from credential_vault_credential
where session_id is null
and status not in ('active', 'revoke')
`
)
+52
View File
@@ -0,0 +1,52 @@
package vault
import (
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
)
// A Repository stores and retrieves the persistent types in the vault
// package. It is not safe to use a repository concurrently.
type Repository struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
scheduler *scheduler.Scheduler
// defaultLimit provides a default for limiting the number of results
// returned from the repo
defaultLimit int
}
// NewRepository creates a new Repository. The returned repository should
// only be used for one transaction and it is not safe for concurrent go
// routines to access it. WithLimit option is used as a repo wide default
// limit applied to all ListX methods.
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, scheduler *scheduler.Scheduler, opt ...Option) (*Repository, error) {
const op = "vault.NewRepository"
switch {
case r == nil:
return nil, errors.New(errors.InvalidParameter, op, "db.Reader")
case w == nil:
return nil, errors.New(errors.InvalidParameter, op, "db.Writer")
case kms == nil:
return nil, errors.New(errors.InvalidParameter, op, "kms")
case scheduler == nil:
return nil, errors.New(errors.InvalidParameter, op, "scheduler")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &Repository{
reader: r,
writer: w,
kms: kms,
scheduler: scheduler,
defaultLimit: opts.withLimit,
}, nil
}
@@ -0,0 +1,259 @@
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/sdk/strutil"
)
// CreateCredentialLibrary inserts l into the repository and returns a new
// CredentialLibrary containing the credential library's PublicId. l is not
// changed. l must contain a valid StoreId. l must not contain a PublicId.
// The PublicId is generated and assigned by this method.
//
// Both l.Name and l.Description are optional. If l.Name is set, it must be
// unique within l.StoreId.
//
// Both l.CreateTime and l.UpdateTime are ignored.
func (r *Repository) CreateCredentialLibrary(ctx context.Context, scopeId string, l *CredentialLibrary, _ ...Option) (*CredentialLibrary, error) {
const op = "vault.(Repository).CreateCredentialLibrary"
if l == nil {
return nil, errors.New(errors.InvalidParameter, op, "nil CredentialLibrary")
}
if l.CredentialLibrary == nil {
return nil, errors.New(errors.InvalidParameter, op, "nil embedded l")
}
if l.StoreId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no store id")
}
if l.VaultPath == "" {
return nil, errors.New(errors.InvalidParameter, op, "no vault path")
}
if l.PublicId != "" {
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
if scopeId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no scope id")
}
l = l.clone()
if l.HttpMethod == "" {
l.HttpMethod = string(MethodGet)
}
id, err := newCredentialLibraryId()
if err != nil {
return nil, errors.Wrap(err, op)
}
l.PublicId = id
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var newCredentialLibrary *CredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
newCredentialLibrary = l.clone()
err := w.Create(ctx, newCredentialLibrary, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_CREATE)))
if err != nil {
return errors.Wrap(err, op)
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s: name %s already exists", l.StoreId, l.Name)))
}
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s", l.StoreId)))
}
return newCredentialLibrary, nil
}
// UpdateCredentialLibrary updates the repository entry for l.PublicId with
// the values in l for the fields listed in fieldMaskPaths. It returns a
// new CredentialLibrary containing the updated values and a count of the
// number of records updated. l is not changed.
//
// l must contain a valid PublicId. Only Name, Description, VaultPath,
// HttpMethod, and HttpRequestBody can be updated. If l.Name is set to a
// non-empty string, it must be unique within l.StoreId.
//
// An attribute of l will be set to NULL in the database if the attribute
// in l is the zero value and it is included in fieldMaskPaths except for
// HttpMethod. If HttpMethod is in the fieldMaskPath but l.HttpMethod
// is not set it will be set to the value "GET". If storage has a value
// for HttpRequestBody when l.HttpMethod is set to GET the update will fail.
func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string, l *CredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*CredentialLibrary, int, error) {
const op = "vault.(Repository).UpdateCredentialLibrary"
if l == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing CredentialLibrary")
}
if l.CredentialLibrary == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded CredentialLibrary")
}
if l.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
if version == 0 {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
if scopeId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
}
l = l.clone()
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(vaultPathField, f):
case strings.EqualFold(httpMethodField, f):
case strings.EqualFold(httpRequestBodyField, f):
default:
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f)
}
}
var dbMask, nullFields []string
dbMask, nullFields = dbcommon.BuildUpdatePaths(
map[string]interface{}{
nameField: l.Name,
descriptionField: l.Description,
vaultPathField: l.VaultPath,
httpMethodField: l.HttpMethod,
httpRequestBodyField: l.HttpRequestBody,
},
fieldMaskPaths,
nil,
)
if strutil.StrListContains(nullFields, httpMethodField) {
dbMask = append(dbMask, httpMethodField)
nullFields = strutil.StrListDelete(nullFields, httpMethodField)
l.HttpMethod = string(MethodGet)
}
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt),
errors.WithMsg("unable to get oplog wrapper"))
}
var rowsUpdated int
var returnedCredentialLibrary *CredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedCredentialLibrary = l.clone()
var err error
rowsUpdated, err = w.Update(ctx, returnedCredentialLibrary, dbMask, nullFields,
db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version))
if err == nil && rowsUpdated > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
return err
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
}
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(l.PublicId))
}
return returnedCredentialLibrary, rowsUpdated, nil
}
// LookupCredentialLibrary returns the CredentialLibrary for publicId.
// Returns nil, nil if no CredentialLibrary is found for publicId.
func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*CredentialLibrary, error) {
const op = "vault.(Repository).LookupCredentialLibrary"
if publicId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no public id")
}
l := allocCredentialLibrary()
l.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, l); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
}
return l, nil
}
// DeleteCredentialLibrary deletes publicId from the repository and returns
// the number of records deleted.
func (r *Repository) DeleteCredentialLibrary(ctx context.Context, scopeId string, publicId string, _ ...Option) (int, error) {
const op = "vault.(Repository).DeleteCredentialLibrary"
if publicId == "" {
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no public id")
}
if scopeId == "" {
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no scope id")
}
l := allocCredentialLibrary()
l.PublicId = publicId
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
_, err = r.writer.DoTx(
ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dl := l.clone()
rowsDeleted, err = w.Delete(ctx, dl, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err == nil && rowsDeleted > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 CredentialLibrary would have been deleted")
}
return err
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", l.PublicId)))
}
return rowsDeleted, nil
}
// ListCredentialLibraries returns a slice of CredentialLibraries for the
// storeId. WithLimit is the only option supported.
func (r *Repository) ListCredentialLibraries(ctx context.Context, storeId string, opt ...Option) ([]*CredentialLibrary, error) {
const op = "vault.(Repository).ListCredentialLibraries"
if storeId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no storeId")
}
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
var libs []*CredentialLibrary
err := r.reader.SearchWhere(ctx, &libs, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(err, op)
}
return libs, nil
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,727 @@
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/sdk/parseutil"
vault "github.com/hashicorp/vault/api"
)
// CreateCredentialStore inserts cs into the repository and returns a new
// CredentialStore containing the credential store's PublicId. cs is not
// changed. cs must not contain a PublicId. The PublicId is generated and
// assigned by this method. cs must contain a valid ScopeId, VaultAddress,
// and Vault token. The Vault token must be renewable, periodic, and
// orphan. CreateCredentialStore calls the /auth/token/renew-self and
// /auth/token/lookup-self Vault endpoints.
//
// Both cs.Name and cs.Description are optional. If cs.Name is set, it must
// be unique within cs.ScopeId. Both cs.CreateTime and cs.UpdateTime are
// ignored.
//
// For more information about the required properties of the Vault token see:
// https://www.vaultproject.io/api-docs/auth/token#period,
// https://www.vaultproject.io/api-docs/auth/token#renewable,
// https://www.vaultproject.io/docs/concepts/tokens#token-hierarchies-and-orphan-tokens,
// https://www.vaultproject.io/docs/concepts/tokens#periodic-tokens, and
// https://www.vaultproject.io/docs/concepts/tokens#token-time-to-live-periodic-tokens-and-explicit-max-ttls.
//
// For more information about the Vault endpoints called by
// CreateCredentialStore see:
// https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self and
// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token-self.
func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialStore, _ ...Option) (*CredentialStore, error) {
const op = "vault.(Repository).CreateCredentialStore"
if cs == nil {
return nil, errors.New(errors.InvalidParameter, op, "nil CredentialStore")
}
if cs.CredentialStore == nil {
return nil, errors.New(errors.InvalidParameter, op, "nil embedded CredentialStore")
}
if cs.ScopeId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no scope id")
}
if len(cs.inputToken) == 0 {
return nil, errors.New(errors.InvalidParameter, op, "no vault token")
}
if cs.VaultAddress == "" {
return nil, errors.New(errors.InvalidParameter, op, "no vault address")
}
if cs.PublicId != "" {
return nil, errors.New(errors.InvalidParameter, op, "public id not empty")
}
if cs.clientCert != nil && len(cs.clientCert.CertificateKey) == 0 {
return nil, errors.New(errors.InvalidParameter, op, "client certificate without private key")
}
cs = cs.clone()
id, err := newCredentialStoreId()
if err != nil {
return nil, errors.Wrap(err, op)
}
cs.PublicId = id
if cs.clientCert != nil {
cs.clientCert.StoreId = id
}
client, err := cs.client()
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client"))
}
tokenLookup, err := client.lookupToken()
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup vault token"))
}
if err := validateTokenLookup(op, tokenLookup); err != nil {
return nil, err
}
available, err := client.capabilities(requiredCapabilities.paths())
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault capabilities"))
}
missing := available.missing(requiredCapabilities)
if len(missing) > 0 {
return nil,
errors.New(errors.VaultTokenMissingCapabilities, op, fmt.Sprintf("missing capabilites: %v", missing))
}
renewedToken, err := client.renewToken()
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to renew vault token"))
}
tokenExpires, err := renewedToken.TokenTTL()
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration"))
}
accessor, err := renewedToken.TokenAccessor()
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault token accessor"))
}
token, err := newToken(id, cs.inputToken, []byte(accessor), tokenExpires)
if err != nil {
return nil, err
}
oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
// encrypt
if err := token.encrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
}
if cs.clientCert != nil {
if err := cs.clientCert.encrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(err, op)
}
}
var newToken *Token
var newClientCertificate *ClientCertificate
var newCredentialStore *CredentialStore
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 3)
ticket, err := w.GetTicket(cs)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
// insert credential store
newCredentialStore = cs.clone()
var csOplogMsg oplog.Message
if err := w.Create(ctx, newCredentialStore, db.NewOplogMsg(&csOplogMsg)); err != nil {
return errors.Wrap(err, op)
}
msgs = append(msgs, &csOplogMsg)
// insert token
newToken = token.clone()
query, values := newToken.insertQuery()
rows, err := w.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if rows > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 token would have been created")
}
msgs = append(msgs, newToken.oplogMessage(db.CreateOp))
newCredentialStore.inputToken = nil
newToken.Token.Token = nil
newToken.Token.CtToken = nil
newCredentialStore.outputToken = newToken
// insert client certificate (if exists)
if cs.clientCert != nil {
newClientCertificate = cs.clientCert.clone()
var clientCertOplogMsg oplog.Message
if err := w.Create(ctx, newClientCertificate, db.NewOplogMsg(&clientCertOplogMsg)); err != nil {
return errors.Wrap(err, op)
}
msgs = append(msgs, &clientCertOplogMsg)
newClientCertificate.CertificateKey = nil
newClientCertificate.CtCertificateKey = nil
newCredentialStore.clientCert = newClientCertificate
}
metadata := cs.oplog(oplog.OpType_OP_TYPE_CREATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", cs.ScopeId, cs.Name)))
}
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", cs.ScopeId)))
}
// Best effort update next run time of token renewal job, but an error should not
// cause update to fail.
// TODO (lcr 05/2021): log error once repo has logger
_ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRenewalJobName, token.renewalIn())
return newCredentialStore, nil
}
func validateTokenLookup(op errors.Op, s *vault.Secret) error {
if s.Data == nil {
return errors.New(errors.InvalidParameter, op, "vault secret is not a token lookup")
}
if s.Data["renewable"] == nil {
return errors.E(errors.WithCode(errors.VaultTokenNotRenewable), errors.WithOp(op))
}
renewable, err := parseutil.ParseBool(s.Data["renewable"])
if err != nil {
return errors.Wrap(err, op)
}
if !renewable {
return errors.E(errors.WithCode(errors.VaultTokenNotRenewable), errors.WithOp(op))
}
if s.Data["orphan"] == nil {
return errors.E(errors.WithCode(errors.VaultTokenNotOrphan), errors.WithOp(op))
}
orphan, err := parseutil.ParseBool(s.Data["orphan"])
if err != nil {
return errors.Wrap(err, op)
}
if !orphan {
return errors.E(errors.WithCode(errors.VaultTokenNotOrphan), errors.WithOp(op))
}
if s.Data["period"] == nil {
return errors.E(errors.WithCode(errors.VaultTokenNotPeriodic), errors.WithOp(op))
}
return nil
}
// LookupCredentialStore returns the CredentialStore for publicId. Returns
// nil, nil if no CredentialStore is found for publicId.
func (r *Repository) LookupCredentialStore(ctx context.Context, publicId string, _ ...Option) (*CredentialStore, error) {
const op = "vault.(Repository).LookupCredentialStore"
if publicId == "" {
return nil, errors.New(errors.InvalidParameter, op, "no public id")
}
agg := allocPublicStore()
agg.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, agg); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
}
return agg.toCredentialStore(), nil
}
type publicStore struct {
PublicId string `gorm:"primary_key"`
ScopeId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
TokenHmac []byte
TokenCreateTime *timestamp.Timestamp
TokenUpdateTime *timestamp.Timestamp
TokenLastRenewalTime *timestamp.Timestamp
TokenExpirationTime *timestamp.Timestamp
ClientCert []byte
ClientCertKeyHmac []byte
}
func allocPublicStore() *publicStore {
return &publicStore{}
}
func (ps *publicStore) toCredentialStore() *CredentialStore {
cs := allocCredentialStore()
cs.PublicId = ps.PublicId
cs.ScopeId = ps.ScopeId
cs.Name = ps.Name
cs.Description = ps.Description
cs.CreateTime = ps.CreateTime
cs.UpdateTime = ps.UpdateTime
cs.Version = ps.Version
cs.VaultAddress = ps.VaultAddress
cs.Namespace = ps.Namespace
cs.CaCert = ps.CaCert
cs.TlsServerName = ps.TlsServerName
cs.TlsSkipVerify = ps.TlsSkipVerify
if ps.TokenHmac != nil {
tk := allocToken()
tk.TokenHmac = ps.TokenHmac
tk.LastRenewalTime = ps.TokenLastRenewalTime
tk.ExpirationTime = ps.TokenExpirationTime
tk.CreateTime = ps.TokenCreateTime
tk.UpdateTime = ps.TokenUpdateTime
cs.outputToken = tk
}
if ps.ClientCert != nil {
cert := allocClientCertificate()
cert.Certificate = ps.ClientCert
cert.CertificateKeyHmac = ps.ClientCertKeyHmac
cs.clientCert = cert
}
return cs
}
// TableName returns the table name for gorm.
func (_ *publicStore) TableName() string { return "credential_vault_store_public" }
// GetPublicId returns the public id.
func (ps *publicStore) GetPublicId() string { return ps.PublicId }
// UpdateCredentialStore updates the repository entry for cs.PublicId with
// the values in cs for the fields listed in fieldMaskPaths. It returns a
// new CredentialStore containing the updated values and a count of the
// number of records updated. cs is not changed.
//
// cs must contain a valid PublicId. Only Name, Description, Namespace,
// TlsServerName, TlsSkipVerify, CaCert, VaultAddress, ClientCertificate,
// ClientCertificateKey, and Token can be changed. If cs.Name is set to a
// non-empty string, it must be unique within cs.ScopeId. If Token is changed,
// the new token must have the same properties defined in CreateCredentialStore
// and UpdateCredentialStore calls the same Vault endpoints described in
// CreateCredentialStore.
//
// An attribute of cs will be set to NULL in the database if the attribute
// in cs is the zero value and it is included in fieldMaskPaths.
func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialStore, version uint32, fieldMaskPaths []string, _ ...Option) (*CredentialStore, int, error) {
const op = "vault.(Repository).UpdateCredentialStore"
if cs == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing CredentialStore")
}
if cs.CredentialStore == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded CredentialStore")
}
if cs.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
}
if version == 0 {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
}
if cs.ScopeId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
}
cs = cs.clone()
var validateToken, updateToken bool
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(namespaceField, f):
case strings.EqualFold(tlsServerNameField, f):
case strings.EqualFold(tlsSkipVerifyField, f):
case strings.EqualFold(caCertField, f):
case strings.EqualFold(vaultAddressField, f):
validateToken = true
case strings.EqualFold(certificateField, f):
case strings.EqualFold(certificateKeyField, f):
case strings.EqualFold(tokenField, f):
if len(cs.inputToken) != 0 {
updateToken = true
validateToken = true
}
default:
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f)
}
}
dbMask, nullFields := dbcommon.BuildUpdatePaths(
map[string]interface{}{
nameField: cs.Name,
descriptionField: cs.Description,
namespaceField: cs.Namespace,
tlsServerNameField: cs.TlsServerName,
tlsSkipVerifyField: cs.TlsSkipVerify,
caCertField: cs.CaCert,
vaultAddressField: cs.VaultAddress,
tokenField: cs.inputToken,
},
fieldMaskPaths,
[]string{
tlsSkipVerifyField,
},
)
var clientCert, clientCertKey []byte
if cs.ClientCertificate() != nil {
clientCert = cs.ClientCertificate().GetCertificate()
clientCertKey = cs.ClientCertificate().GetCertificateKey()
}
certDbMask, certNullFields := dbcommon.BuildUpdatePaths(
map[string]interface{}{
certificateField: clientCert,
certificateKeyField: clientCertKey,
},
fieldMaskPaths, nil,
)
if len(certNullFields) != 0 && len(certNullFields) != 2 {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "attempting to unset a required field on a client cert")
}
if len(append(dbMask, certDbMask...)) == 0 && len(append(nullFields, certNullFields...)) == 0 {
return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask")
}
var filteredDbMask, filteredNullFields []string
for _, f := range dbMask {
switch {
case strings.EqualFold(tokenField, f):
default:
filteredDbMask = append(filteredDbMask, f)
}
}
for _, f := range nullFields {
switch {
case strings.EqualFold(tokenField, f):
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "attempting to unset the value for token")
default:
filteredNullFields = append(filteredNullFields, f)
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected,
errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
databaseWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, db.NoRowsAffected,
errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper"))
}
ps, err := r.lookupPrivateStore(ctx, cs.GetPublicId())
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to lookup private credential store"))
}
if ps == nil {
return nil, db.NoRowsAffected, errors.New(errors.RecordNotFound, op, fmt.Sprintf("credential store %s", cs.PublicId))
}
origStore := ps.toCredentialStore()
origStore.inputToken = ps.Token
if len(ps.ClientCert) > 0 {
origStore.clientCert, err = NewClientCertificate(ps.ClientCert, ps.ClientKey)
}
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("can't recreate client certificate for vault client creation"))
}
updatedStore := origStore.applyUpdate(cs, fieldMaskPaths)
if len(certDbMask) > 0 && updatedStore.clientCert != nil {
if err := updatedStore.clientCert.encrypt(ctx, databaseWrapper); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
}
var token *Token
client, err := updatedStore.client()
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get client for updated store"))
}
if validateToken {
tokenLookup, err := client.lookupToken()
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("cannot lookup token for updated store"))
}
if err := validateTokenLookup(op, tokenLookup); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
available, err := client.capabilities(requiredCapabilities.paths())
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault capabilities"))
}
missing := available.missing(requiredCapabilities)
if len(missing) > 0 {
return nil,
db.NoRowsAffected,
errors.New(errors.VaultTokenMissingCapabilities, op, fmt.Sprintf("missing capabilites: %v", missing))
}
}
if updateToken {
renewedToken, err := client.renewToken()
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to renew vault token"))
}
tokenExpires, err := renewedToken.TokenTTL()
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration"))
}
accessor, err := renewedToken.TokenAccessor()
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault token accessor"))
}
if token, err = newToken(cs.GetPublicId(), cs.inputToken, []byte(accessor), tokenExpires); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
// encrypt token
if err := token.encrypt(ctx, databaseWrapper); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
}
var rowsUpdated int
var returnedCredentialStore *CredentialStore
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
msgs := make([]*oplog.Message, 0, 3)
ticket, err := w.GetTicket(cs)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
cs := cs.clone()
var csOplogMsg oplog.Message
switch {
case len(filteredDbMask) == 0 && len(filteredNullFields) == 0:
// the credential store's fields are not being updated,
// just it's token or client certificate, so we need to
// just update the credential store's version.
cs.Version = version + 1
rowsUpdated, err = w.Update(ctx, cs, []string{"Version"}, nil, db.NewOplogMsg(&csOplogMsg), db.WithVersion(&version))
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to update credential store version"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated credential store version and %d rows updated", rowsUpdated))
}
default:
rowsUpdated, err = w.Update(ctx, cs, filteredDbMask, filteredNullFields, db.NewOplogMsg(&csOplogMsg), db.WithVersion(&version))
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to update credential store"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated credential store and %d rows updated", rowsUpdated))
}
}
msgs = append(msgs, &csOplogMsg)
switch {
case len(certNullFields) == 2:
// Delete the certificate
deleteCert := allocClientCertificate()
deleteCert.StoreId = cs.GetPublicId()
query, values := deleteCert.deleteQuery()
rows, err := w.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to delete client certificate"))
}
if rows > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 client certificate would have been deleted")
}
msgs = append(msgs, deleteCert.oplogMessage(db.DeleteOp))
case len(certDbMask) > 0:
if updatedStore.clientCert == nil {
return errors.New(errors.InvalidParameter, op, "updated cert")
}
query, values := updatedStore.clientCert.insertQuery()
rows, err := w.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to upsert client certificate"))
}
if rows > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 client certificate would have been upserted")
}
}
if updateToken {
query, values := token.insertQuery()
rows, err := w.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if rows > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 token would have been created")
}
msgs = append(msgs, token.oplogMessage(db.CreateOp))
}
publicId := cs.PublicId
agg := allocPublicStore()
agg.PublicId = publicId
if err := reader.LookupByPublicId(ctx, agg); err != nil {
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup credential store: %s", publicId)))
}
returnedCredentialStore = agg.toCredentialStore()
metadata := cs.oplog(oplog.OpType_OP_TYPE_UPDATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", cs.Name, cs.PublicId))
}
return nil, db.NoRowsAffected, err
}
if updateToken && token != nil {
// Best effort update next run time of token renewal job, but an error should not
// cause update to fail.
// TODO (lcr 05/2021): log error once repo has logger
_ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRenewalJobName, token.renewalIn())
}
return returnedCredentialStore, rowsUpdated, nil
}
// ListCredentialStores returns a slice of CredentialStores for the
// scopeIds. WithLimit is the only option supported.
func (r *Repository) ListCredentialStores(ctx context.Context, scopeIds []string, opt ...Option) ([]*CredentialStore, error) {
const op = "vault.(Repository).ListCredentialStores"
if len(scopeIds) == 0 {
return nil, errors.New(errors.InvalidParameter, op, "no scopeIds")
}
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
var credentialStores []*publicStore
err := r.reader.SearchWhere(ctx, &credentialStores, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(err, op)
}
var out []*CredentialStore
for _, ca := range credentialStores {
out = append(out, ca.toCredentialStore())
}
return out, nil
}
// DeleteCredentialStore deletes publicId from the repository and returns
// the number of records deleted. All options are ignored.
func (r *Repository) DeleteCredentialStore(ctx context.Context, publicId string, _ ...Option) (int, error) {
const op = "vault.(Repository).DeleteCredentialStore"
if publicId == "" {
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no public id")
}
cs := allocCredentialStore()
cs.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, cs); err != nil {
if errors.IsNotFoundError(err) {
return db.NoRowsAffected, nil
}
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
}
if cs.ScopeId == "" {
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no scope id")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rows int
query, values := cs.softDeleteQuery()
_, err = r.writer.DoTx(
ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
var msgs []*oplog.Message
ticket, err := w.GetTicket(cs)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
}
rows, err = w.Exec(ctx, query, values)
if err != nil {
return errors.Wrap(err, op)
}
if rows > 1 {
return errors.New(errors.MultipleRecords, op, "more than 1 credential store would have been deleted")
}
msg := cs.oplogMessage(db.UpdateOp)
msg.FieldMaskPaths = []string{"DeleteTime"}
msgs = append(msgs, msg)
metadata := cs.oplog(oplog.OpType_OP_TYPE_UPDATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", cs.PublicId)))
}
if rows > 0 {
// Schedule token revocation and credential store cleanup jobs to run immediately
_ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRevocationJobName, 0)
_ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, credentialStoreCleanupJobName, 0)
}
return rows, nil
}
Loaded 100 of 216 files, more files were not shown because too many files have changed in this diff. Show more