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:
216 files changed
+38675
-1941
No files matched your search
Generated
+1
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Run Acceptance Tests"
|
||||
no_output_timeout: 15m
|
||||
command: |
|
||||
make test-ci
|
||||
- slack/status:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -51,4 +51,7 @@ const (
|
||||
EmailField = "email"
|
||||
ManagedGroupIdsField = "managed_group_ids"
|
||||
FilterField = "filter"
|
||||
CredentialStoreIdField = "credential_store_id"
|
||||
CredentialLibraryIdsField = "credential_library_ids"
|
||||
CredentialLibrariesField = "credential_libraries"
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -99,6 +99,7 @@ var (
|
||||
int32ValueName = (&wrapperspb.Int32Value{}).ProtoReflect().Descriptor().FullName()
|
||||
structValueName = (&_struct.Struct{}).ProtoReflect().Descriptor().FullName()
|
||||
timestampName = (×tamppb.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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package vault provides access to credentials retrieved from a Vault
|
||||
// server.
|
||||
package vault
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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')
|
||||
`
|
||||
)
|
||||
@@ -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
Reference in new issue
Block a user