Merge pull request #2860 from hashicorp/llb-vault-ssh-cert-injection

Add vault ssh certificate credential library

This adds a new credential library type, `vault-ssh-certificate`, which
can be used with vault's ssh secret engine to generate certificates to
use as credentials for ssh. This new credential library can be created
using:

    boundary credential-libraries create vault-ssh-certificate

See the help output for a full list of the options supported.

This also renames the `vault` credential library subtype to
`vault-generic`. The previous subtype of `vault` will continue to work,
thus the following commands are equivalent:

    boundary credential-libraries create vault-generic
    boundary credential-libraries create vault

See: https://developer.hashicorp.com/vault/docs/secrets/ssh
This commit is contained in:
Timothy Messier authored and GitHub committed 2023-02-02 12:44:20 -05:00
commit db42eafd7a
69 files changed
+8784 -705

No files matched your search

+19 -2
View File
@@ -69,7 +69,7 @@ jobs:
echo "::set-output name=product-minor-version::$MINOR_VERSION"
verify-product-metadata:
needs:
needs:
- set-product-version
- product-metadata
runs-on: ubuntu-latest
@@ -422,7 +422,7 @@ jobs:
uses: actions/cache@v3
with:
path: /tmp/bats-cli-ui-deps
key: enos-bats-cli-ui-deps-jq-1.6-password-store-1.7.4
key: enos-bats-cli-ui-deps-jq-1.6-password-store-1.7.4-vault-1.12.2
- name: Set up Node for Bats install
uses: actions/setup-node@v3
with:
@@ -453,6 +453,13 @@ jobs:
cd /tmp/bats-cli-ui-deps/pass/password-store-1.7.4
sudo make install
pass init ${{ secrets.ENOS_GPG_UID }}
- name: Download Vault AMD64 binary for integration testing
if: steps.dep-cache.outputs.cache-hit != 'true'
run: |
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/bats-cli-ui-deps/vault.zip
- name: Install Vault
run: |
unzip /tmp/bats-cli-ui-deps/vault.zip -d /usr/local/bin
- name: Download Linux AMD64 Boundary bundle
id: download
uses: actions/download-artifact@v3
@@ -479,6 +486,16 @@ jobs:
bash --version
echo "boundary version:"
boundary version
echo "vault version:"
vault version
- name: Run cli bats tests
run: |
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=boundarytok
make -C internal/tests/cli test-vault-up
until vault status; do docker container inspect boundary-cli-tests-vault &> /dev/null || exit -1; sleep 1; done
make test-cli
- name: Cleanup
if: success() || failure()
run: |
make -C internal/tests/cli test-vault-down
+20
View File
@@ -18,6 +18,22 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
0.14.0, this will become an error instead. As a consequence, it means that the
fallback logic for targets that did not have a default port defined is no
longer in service; all targets must now have a default port defined.
* With the introduction of `vault-ssh-certificate` credential libraries, the
`vault` credential library subtype is being renamed to `vault-generic` to
denote it as a credential library that can be used in a generalized way to
issue credentials from vault. Existing credential libraries with the
subtype of `vault` will be updated to `vault-generic`. The subtype of
`vault` will still be accepted as a valid subtype in API requests to the
credential libraries endpoints, but is deprecated. Instead `vault-generic`
should be used. In addition the `boundary credential-libraries create
vault` and `boundary credential-libraries update vault` subcommands will
still function, but are deprecated. Instead `boundary credential-libraries
create vault-generic` and `boundary credential-libraries update
vault-generic` should be used. Also note that any credential library created
using the subtype of `vault`, either via the API or via the deprecated
subcommand, will have the subtype set to `vault-generic`. The deprecated
subtype and subcommands will be removed in boundary 0.14.0, at which point
`vault-generic` must be used.
### New and Improved
@@ -39,6 +55,10 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
multiple workers together to access services hidden under layers of network security. Multi-hop
workers can also establish a TCP session through multiple workers, with the ability to reverse
proxy and establish a connection.
* Vault SSH certificate credential library: A new credential library that uses
the vault ssh secret engine to generate ssh private key and certificates. The
library can be used as an injected application credential source for targets
that support credential injection. ([PR](https://github.com/hashicorp/boundary/pull/2860))
### Bug Fixes
@@ -90,7 +90,7 @@ func (c *Client) ApiClient() *api.Client {
return c.client
}
func (c *Client) Create(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialLibraryCreateResult, error) {
func (c *Client) Create(ctx context.Context, resourceType string, credentialStoreId string, opt ...Option) (*CredentialLibraryCreateResult, error) {
if credentialStoreId == "" {
return nil, fmt.Errorf("empty credentialStoreId value passed into Create request")
}
@@ -100,6 +100,11 @@ func (c *Client) Create(ctx context.Context, credentialStoreId string, opt ...Op
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["credential_store_id"] = credentialStoreId
+168
View File
@@ -110,6 +110,30 @@ func DefaultCredentialType() Option {
}
}
func WithVaultSSHCertificateCredentialLibraryCriticalOptions(inCriticalOptions map[string]string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["critical_options"] = inCriticalOptions
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryCriticalOptions() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["critical_options"] = nil
o.postMap["attributes"] = val
}
}
func WithDescription(inDescription string) Option {
return func(o *options) {
o.postMap["description"] = inDescription
@@ -122,6 +146,30 @@ func DefaultDescription() Option {
}
}
func WithVaultSSHCertificateCredentialLibraryExtensions(inExtensions map[string]string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["extensions"] = inExtensions
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryExtensions() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["extensions"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultCredentialLibraryHttpMethod(inHttpMethod string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
@@ -170,6 +218,78 @@ func DefaultVaultCredentialLibraryHttpRequestBody() Option {
}
}
func WithVaultSSHCertificateCredentialLibraryKeyBits(inKeyBits uint32) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_bits"] = inKeyBits
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryKeyBits() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_bits"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultSSHCertificateCredentialLibraryKeyId(inKeyId string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_id"] = inKeyId
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryKeyId() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_id"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultSSHCertificateCredentialLibraryKeyType(inKeyType string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_type"] = inKeyType
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryKeyType() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["key_type"] = nil
o.postMap["attributes"] = val
}
}
func WithName(inName string) Option {
return func(o *options) {
o.postMap["name"] = inName
@@ -193,3 +313,51 @@ func WithVaultCredentialLibraryPath(inPath string) Option {
o.postMap["attributes"] = val
}
}
func WithVaultSSHCertificateCredentialLibraryPath(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
}
}
func WithVaultSSHCertificateCredentialLibraryTtl(inTtl string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["ttl"] = inTtl
o.postMap["attributes"] = val
}
}
func DefaultVaultSSHCertificateCredentialLibraryTtl() Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["ttl"] = nil
o.postMap["attributes"] = val
}
}
func WithVaultSSHCertificateCredentialLibraryUsername(inUsername string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["username"] = inUsername
o.postMap["attributes"] = val
}
}
@@ -0,0 +1,44 @@
// Code generated by "make api"; DO NOT EDIT.
package credentiallibraries
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
type VaultSSHCertificateCredentialLibraryAttributes struct {
Path string `json:"path,omitempty"`
Username string `json:"username,omitempty"`
KeyType string `json:"key_type,omitempty"`
KeyBits uint32 `json:"key_bits,omitempty"`
Ttl string `json:"ttl,omitempty"`
KeyId string `json:"key_id,omitempty"`
CriticalOptions map[string]string `json:"critical_options,omitempty"`
Extensions map[string]string `json:"extensions,omitempty"`
}
func AttributesMapToVaultSSHCertificateCredentialLibraryAttributes(in map[string]interface{}) (*VaultSSHCertificateCredentialLibraryAttributes, error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out VaultSSHCertificateCredentialLibraryAttributes
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
TagName: "json",
})
if err != nil {
return nil, fmt.Errorf("error creating mapstructure decoder: %w", err)
}
if err := dec.Decode(in); err != nil {
return nil, fmt.Errorf("error decoding: %w", err)
}
return &out, nil
}
func (pt *CredentialLibrary) GetVaultSSHCertificateCredentialLibraryAttributes() (*VaultSSHCertificateCredentialLibraryAttributes, error) {
if pt.Type != "vaultsshcertificate" {
return nil, fmt.Errorf("asked to fetch %s-type attributes but credential-library is of type %s", "vaultsshcertificate", pt.Type)
}
return AttributesMapToVaultSSHCertificateCredentialLibraryAttributes(pt.Attributes)
}
+1
View File
@@ -161,6 +161,7 @@ require (
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
+2
View File
@@ -980,6 +980,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw=
+47 -1
View File
@@ -518,12 +518,58 @@ var inputStructs = []*structInfo{
mapstructureConversionTemplate,
},
},
{
inProto: &credentiallibraries.VaultSSHCertificateCredentialLibraryAttributes{},
outFile: "credentiallibraries/vault_ssh_certificate_credential_library_attributes.gen.go",
subtypeName: "VaultSSHCertificateCredentialLibrary",
fieldOverrides: []fieldInfo{
{
Name: "Path",
SkipDefault: true,
},
{
Name: "Username",
SkipDefault: true,
},
{
Name: "CriticalOptions",
FieldType: "map[string]string",
},
{
Name: "Extensions",
FieldType: "map[string]string",
},
},
parentTypeName: "CredentialLibrary",
templates: []*template.Template{
mapstructureConversionTemplate,
},
},
{
inProto: &credentiallibraries.CredentialLibrary{},
outFile: "credentiallibraries/credential_library.gen.go",
templates: []*template.Template{
clientTemplate,
commonCreateTemplate,
template.Must(template.New("").Funcs(
template.FuncMap{
"snakeCase": snakeCase,
"funcName": func() string {
return "Create"
},
"apiAction": func() string {
return ""
},
"extraRequiredParams": func() []requiredParam {
return []requiredParam{
{
Name: "resourceType",
Typ: "string",
PostType: "type",
},
}
},
},
).Parse(createTemplateStr)),
readTemplate,
updateTemplate,
deleteTemplate,
+25 -1
View File
@@ -364,6 +364,18 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "create",
}, nil
},
"credential-libraries create vault-generic": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultGenericCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-libraries create vault-ssh-certificate": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultSshCertificateCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credential-libraries update": func() (cli.Command, error) {
return &credentiallibrariescmd.Command{
Command: base.NewCommand(ui),
@@ -371,7 +383,19 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
}, nil
},
"credential-libraries update vault": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultCommand{
return &credentiallibrariescmd.VaultGenericCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"credential-libraries update vault-generic": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultGenericCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"credential-libraries update vault-ssh-certificate": func() (cli.Command, error) {
return &credentiallibrariescmd.VaultSshCertificateCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
@@ -140,6 +140,16 @@ func printItemTable(item *credentiallibraries.CredentialLibrary, resp *api.Respo
nonAttributeMap["Type"] = item.Type
}
var keySubstMap map[string]string
switch item.Type {
case "vault":
fallthrough
case "vault-generic":
keySubstMap = genericKeySubstMap
case "vault-ssh-certificate":
keySubstMap = sshCertKeySubstMap
}
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
ret := []string{
@@ -189,8 +199,19 @@ func printItemTable(item *credentiallibraries.CredentialLibrary, resp *api.Respo
return base.WrapForHelpText(ret)
}
var keySubstMap = map[string]string{
var genericKeySubstMap = map[string]string{
"path": "Path",
"http_method": "HTTP Method",
"http_request_body": "HTTP Request Body",
}
var sshCertKeySubstMap = map[string]string{
"path": "Path",
"username": "Username",
"key_type": "Key Type",
"key_bits": "Key Bits",
"ttl": "TTL",
"key_id": "Key ID",
"critical_options": "Critical Options",
"extensions": "Extensions",
}
@@ -0,0 +1,275 @@
// 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/go-secure-stdlib/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initVaultGenericFlags() {
flagsOnce.Do(func() {
extraFlags := extraVaultGenericActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsVaultGenericMap[k] = append(flagsVaultGenericMap[k], v...)
}
})
}
var (
_ cli.Command = (*VaultGenericCommand)(nil)
_ cli.CommandAutocomplete = (*VaultGenericCommand)(nil)
)
type VaultGenericCommand struct {
*base.Command
Func string
plural string
extraVaultGenericCmdVars
}
func (c *VaultGenericCommand) AutocompleteArgs() complete.Predictor {
initVaultGenericFlags()
return complete.PredictAnything
}
func (c *VaultGenericCommand) AutocompleteFlags() complete.Flags {
initVaultGenericFlags()
return c.Flags().Completions()
}
func (c *VaultGenericCommand) Synopsis() string {
if extra := extraVaultGenericSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential library"
synopsisStr = fmt.Sprintf("%s %s", "vault-generic-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *VaultGenericCommand) Help() string {
initVaultGenericFlags()
var helpStr string
helpMap := common.HelpMap("credential library")
switch c.Func {
default:
helpStr = c.extraVaultGenericHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsVaultGenericMap = map[string][]string{
"create": {"credential-store-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *VaultGenericCommand) Flags() *base.FlagSets {
if len(flagsVaultGenericMap[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-generic-type credential library", flagsVaultGenericMap, c.Func)
extraVaultGenericFlagsFunc(c, set, f)
return set
}
func (c *VaultGenericCommand) Run(args []string) int {
initVaultGenericFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "vault-generic-type credential library"
switch c.Func {
case "list":
c.plural = "vault-generic-type credential librarys"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsVaultGenericMap[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(flagsVaultGenericMap[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 c.WrapperCleanupFunc != nil {
defer func() {
if err := c.WrapperCleanupFunc(); err != nil {
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
}
}()
}
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
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 := extraVaultGenericFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var resp *api.Response
var item *credentiallibraries.CredentialLibrary
var createResult *credentiallibraries.CredentialLibraryCreateResult
var updateResult *credentiallibraries.CredentialLibraryUpdateResult
switch c.Func {
case "create":
createResult, err = credentiallibrariesClient.Create(c.Context, "vault-generic", c.FlagCredentialStoreId, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = createResult.GetResponse()
item = createResult.GetItem()
case "update":
updateResult, err = credentiallibrariesClient.Update(c.Context, c.FlagId, version, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = updateResult.GetResponse()
item = updateResult.GetItem()
}
resp, item, err = executeExtraVaultGenericActions(c, resp, item, err, credentiallibrariesClient, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
output, err := printCustomVaultGenericActionOutput(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(item, resp))
case "json":
if ok := c.PrintJsonItem(resp); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
func (c *VaultGenericCommand) checkFuncError(err error) int {
if err == nil {
return 0
}
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
var (
extraVaultGenericActionsFlagsMapFunc = func() map[string][]string { return nil }
extraVaultGenericSynopsisFunc = func(*VaultGenericCommand) string { return "" }
extraVaultGenericFlagsFunc = func(*VaultGenericCommand, *base.FlagSets, *base.FlagSet) {}
extraVaultGenericFlagsHandlingFunc = func(*VaultGenericCommand, *base.FlagSets, *[]credentiallibraries.Option) bool { return true }
executeExtraVaultGenericActions = func(_ *VaultGenericCommand, inResp *api.Response, inItem *credentiallibraries.CredentialLibrary, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (*api.Response, *credentiallibraries.CredentialLibrary, error) {
return inResp, inItem, inErr
}
printCustomVaultGenericActionOutput = func(*VaultGenericCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,174 @@
package credentiallibrariescmd
import (
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/go-secure-stdlib/parseutil"
)
func init() {
extraVaultGenericFlagsFunc = extraVaultGenericFlagsFuncImpl
extraVaultGenericActionsFlagsMapFunc = extraVaultGenericActionsFlagsMapFuncImpl
extraVaultGenericFlagsHandlingFunc = extraVaultGenericFlagHandlingFuncImpl
}
const (
pathFlagName = "vault-path"
httpMethodFlagName = "vault-http-method"
httpRequestBodyFlagName = "vault-http-request-body"
credentialTypeFlagName = "credential-type"
credentialMappingFlagName = "credential-mapping-override"
)
type extraVaultGenericCmdVars struct {
flagPath string
flagHttpMethod string
flagHttpRequestBody string
flagCredentialType string
flagCredentialMapping []base.CombinedSliceFlagValue
}
func extraVaultGenericActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialTypeFlagName,
credentialMappingFlagName,
},
"update": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialMappingFlagName,
},
}
return flags
}
func extraVaultGenericFlagsFuncImpl(c *VaultGenericCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Vault Credential Library Options")
for _, name := range flagsVaultGenericMap[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.",
})
case credentialTypeFlagName:
f.StringVar(&base.StringVar{
Name: credentialTypeFlagName,
Target: &c.flagCredentialType,
Usage: "The type of credential this library will issue, defaults to Unspecified.",
})
case credentialMappingFlagName:
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: credentialMappingFlagName,
Target: &c.flagCredentialMapping,
KvSplit: true,
Usage: "The credential mapping override.",
})
}
}
}
func extraVaultGenericFlagHandlingFuncImpl(c *VaultGenericCommand, _ *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, _ := parseutil.ParsePath(c.flagHttpRequestBody)
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(rb))
}
switch c.flagCredentialType {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultCredentialType())
default:
*opts = append(*opts, credentiallibraries.WithCredentialType(c.flagCredentialType))
}
switch len(c.flagCredentialMapping) {
case 0:
case 1:
if len(c.flagCredentialMapping[0].Keys) == 1 && c.flagCredentialMapping[0].Keys[0] == "null" && c.flagCredentialMapping[0].Value == nil {
*opts = append(*opts, credentiallibraries.DefaultCredentialMappingOverrides())
break
}
fallthrough
default:
mappings := make(map[string]any, len(c.flagCredentialMapping))
for _, mapping := range c.flagCredentialMapping {
switch {
case len(mapping.Keys) != 1 || mapping.Keys[0] == "" || mapping.Value == nil || mapping.Value.GetValue() == "":
// mapping override does not support key segments (e.g. 'x.y=z')
c.UI.Error("Credential mapping override must be in the format 'key=value', 'key=null' to clear field or 'null' to clear all.")
return false
case mapping.Value.GetValue() == "null":
// user provided 'key=null' indicating the field specific override should
// be cleared, set map value to nil
mappings[mapping.Keys[0]] = nil
default:
mappings[mapping.Keys[0]] = mapping.Value.GetValue()
}
}
*opts = append(*opts, credentiallibraries.WithCredentialMappingOverrides(mappings))
}
return true
}
func (c *VaultGenericCommand) extraVaultGenericHelpFunc(_ 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-generic-type credential library. Example:",
"",
` $ boundary credential-libraries create vault-generic -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-generic-type credential library given its ID. Example:",
"",
` $ boundary credential-libraries update vault-generic -id clvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
@@ -0,0 +1,275 @@
// 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/go-secure-stdlib/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initVaultSshCertificateFlags() {
flagsOnce.Do(func() {
extraFlags := extraVaultSshCertificateActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsVaultSshCertificateMap[k] = append(flagsVaultSshCertificateMap[k], v...)
}
})
}
var (
_ cli.Command = (*VaultSshCertificateCommand)(nil)
_ cli.CommandAutocomplete = (*VaultSshCertificateCommand)(nil)
)
type VaultSshCertificateCommand struct {
*base.Command
Func string
plural string
extraVaultSshCertificateCmdVars
}
func (c *VaultSshCertificateCommand) AutocompleteArgs() complete.Predictor {
initVaultSshCertificateFlags()
return complete.PredictAnything
}
func (c *VaultSshCertificateCommand) AutocompleteFlags() complete.Flags {
initVaultSshCertificateFlags()
return c.Flags().Completions()
}
func (c *VaultSshCertificateCommand) Synopsis() string {
if extra := extraVaultSshCertificateSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential library"
synopsisStr = fmt.Sprintf("%s %s", "vault-ssh-certificate-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *VaultSshCertificateCommand) Help() string {
initVaultSshCertificateFlags()
var helpStr string
helpMap := common.HelpMap("credential library")
switch c.Func {
default:
helpStr = c.extraVaultSshCertificateHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsVaultSshCertificateMap = map[string][]string{
"create": {"credential-store-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *VaultSshCertificateCommand) Flags() *base.FlagSets {
if len(flagsVaultSshCertificateMap[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-ssh-certificate-type credential library", flagsVaultSshCertificateMap, c.Func)
extraVaultSshCertificateFlagsFunc(c, set, f)
return set
}
func (c *VaultSshCertificateCommand) Run(args []string) int {
initVaultSshCertificateFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "vault-ssh-certificate-type credential library"
switch c.Func {
case "list":
c.plural = "vault-ssh-certificate-type credential librarys"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsVaultSshCertificateMap[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(flagsVaultSshCertificateMap[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 c.WrapperCleanupFunc != nil {
defer func() {
if err := c.WrapperCleanupFunc(); err != nil {
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
}
}()
}
if err != nil {
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
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 := extraVaultSshCertificateFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var resp *api.Response
var item *credentiallibraries.CredentialLibrary
var createResult *credentiallibraries.CredentialLibraryCreateResult
var updateResult *credentiallibraries.CredentialLibraryUpdateResult
switch c.Func {
case "create":
createResult, err = credentiallibrariesClient.Create(c.Context, "vault-ssh-certificate", c.FlagCredentialStoreId, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = createResult.GetResponse()
item = createResult.GetItem()
case "update":
updateResult, err = credentiallibrariesClient.Update(c.Context, c.FlagId, version, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = updateResult.GetResponse()
item = updateResult.GetItem()
}
resp, item, err = executeExtraVaultSshCertificateActions(c, resp, item, err, credentiallibrariesClient, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
output, err := printCustomVaultSshCertificateActionOutput(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(item, resp))
case "json":
if ok := c.PrintJsonItem(resp); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
func (c *VaultSshCertificateCommand) checkFuncError(err error) int {
if err == nil {
return 0
}
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
var (
extraVaultSshCertificateActionsFlagsMapFunc = func() map[string][]string { return nil }
extraVaultSshCertificateSynopsisFunc = func(*VaultSshCertificateCommand) string { return "" }
extraVaultSshCertificateFlagsFunc = func(*VaultSshCertificateCommand, *base.FlagSets, *base.FlagSet) {}
extraVaultSshCertificateFlagsHandlingFunc = func(*VaultSshCertificateCommand, *base.FlagSets, *[]credentiallibraries.Option) bool { return true }
executeExtraVaultSshCertificateActions = func(_ *VaultSshCertificateCommand, inResp *api.Response, inItem *credentiallibraries.CredentialLibrary, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (*api.Response, *credentiallibraries.CredentialLibrary, error) {
return inResp, inItem, inErr
}
printCustomVaultSshCertificateActionOutput = func(*VaultSshCertificateCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,264 @@
package credentiallibrariescmd
import (
"fmt"
"strconv"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
)
func init() {
extraVaultSshCertificateFlagsFunc = extraVaultSshCertificateFlagsFuncImpl
extraVaultSshCertificateActionsFlagsMapFunc = extraVaultSshCertificateActionsFlagsMapFuncImpl
extraVaultSshCertificateFlagsHandlingFunc = extraVaultSshCertificateFlagHandlingFuncImpl
}
const (
usernameName = "username"
keyTypeName = "key-type"
keyBitsName = "key-bits"
ttlName = "ttl"
keyIdName = "key-id"
criticalOptionsName = "critical-options"
piecewiseCriticalOptionsName = "critical-option"
extensionsName = "extensions"
piecewiseExtensionName = "extension"
)
type extraVaultSshCertificateCmdVars struct {
flagPath string
flagUsername string
flagKeyType string
flagKeyBits string
flagTtl string
flagKeyId string
flagCriticalOptions string
flagCriticalOpts []base.CombinedSliceFlagValue
flagExtensions string
flagExtens []base.CombinedSliceFlagValue
}
func extraVaultSshCertificateActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
pathFlagName,
usernameName,
keyTypeName,
keyBitsName,
ttlName,
keyIdName,
criticalOptionsName,
piecewiseCriticalOptionsName,
extensionsName,
piecewiseExtensionName,
},
"update": {
pathFlagName,
usernameName,
keyTypeName,
keyBitsName,
ttlName,
keyIdName,
criticalOptionsName,
piecewiseCriticalOptionsName,
extensionsName,
piecewiseExtensionName,
},
}
return flags
}
func extraVaultSshCertificateFlagsFuncImpl(c *VaultSshCertificateCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Vault SSH Certificate Credential Library Options")
for _, name := range flagsVaultSshCertificateMap[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 usernameName:
f.StringVar(&base.StringVar{
Name: usernameName,
Target: &c.flagUsername,
Usage: "The username to use with the ssh certificate.",
})
case keyTypeName:
f.StringVar(&base.StringVar{
Name: keyTypeName,
Target: &c.flagKeyType,
Usage: "The key type for the generated ssh private key. One of: ed25519, ecdsa, rsa.",
})
case keyBitsName:
f.StringVar(&base.StringVar{
Name: keyBitsName,
Target: &c.flagKeyBits,
Usage: "The number of bits when generating the ssh private key. Depends on key_type. If ed25519 this should not be set, or set to 0, if ecdsa one of 256, 384, 521, if rsa one of 2048, 3072, 4096.",
})
case ttlName:
f.StringVar(&base.StringVar{
Name: ttlName,
Target: &c.flagTtl,
Usage: "The time-to-live for the generated certificate.",
})
case keyIdName:
f.StringVar(&base.StringVar{
Name: keyIdName,
Target: &c.flagKeyId,
Usage: "The key id that the created certificate should have.",
})
}
}
criticalOptsInput := common.CombinedSliceFlagValuePopulationInput{
FlagSet: f,
FlagNames: flagsVaultSshCertificateMap[c.Func],
FullPopulationFlag: &c.flagCriticalOptions,
FullPopulationInputName: criticalOptionsName,
PiecewisePopulationFlag: &c.flagCriticalOpts,
PiecewisePopulationInputBaseName: piecewiseCriticalOptionsName,
PiecewiseNoProtoCompat: true,
}
common.PopulateCombinedSliceFlagValue(criticalOptsInput)
extensionsInput := common.CombinedSliceFlagValuePopulationInput{
FlagSet: f,
FlagNames: flagsVaultSshCertificateMap[c.Func],
FullPopulationFlag: &c.flagExtensions,
FullPopulationInputName: extensionsName,
PiecewisePopulationFlag: &c.flagExtens,
PiecewisePopulationInputBaseName: piecewiseExtensionName,
PiecewiseNoProtoCompat: true,
}
common.PopulateCombinedSliceFlagValue(extensionsInput)
}
func extraVaultSshCertificateFlagHandlingFuncImpl(c *VaultSshCertificateCommand, _ *base.FlagSets, opts *[]credentiallibraries.Option) bool {
switch c.flagPath {
case "":
default:
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryPath(c.flagPath))
}
switch c.flagUsername {
case "":
default:
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryUsername(c.flagUsername))
}
switch c.flagKeyType {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryKeyType())
default:
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyType(c.flagKeyType))
}
switch c.flagKeyBits {
case "":
case "0", "null":
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryKeyBits())
default:
var final uint32
keyBits, err := strconv.ParseUint(c.flagKeyBits, 10, 32)
if err != nil {
c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagKeyBits, err))
return false
}
final = uint32(keyBits)
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyBits(final))
}
switch c.flagTtl {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryTtl())
default:
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryTtl(c.flagTtl))
}
switch c.flagKeyId {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryKeyId())
default:
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryKeyId(c.flagKeyId))
}
if err := common.HandleAttributeFlags(
c.Command,
piecewiseCriticalOptionsName,
c.flagCriticalOptions,
c.flagCriticalOpts,
func() {
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryCriticalOptions())
},
func(in map[string]any) {
inn := make(map[string]string, len(in))
for k, v := range in {
switch vv := v.(type) {
case nil:
inn[k] = ""
case string:
inn[k] = vv
default:
continue
}
}
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryCriticalOptions(inn))
}); err != nil {
return false
}
if err := common.HandleAttributeFlags(
c.Command,
piecewiseExtensionName,
c.flagExtensions,
c.flagExtens,
func() {
*opts = append(*opts, credentiallibraries.DefaultVaultSSHCertificateCredentialLibraryExtensions())
},
func(in map[string]any) {
inn := make(map[string]string, len(in))
for k, v := range in {
switch vv := v.(type) {
case nil:
inn[k] = ""
case string:
inn[k] = vv
default:
continue
}
}
*opts = append(*opts, credentiallibraries.WithVaultSSHCertificateCredentialLibraryExtensions(inn))
}); err != nil {
return false
}
return true
}
func (c *VaultSshCertificateCommand) extraVaultSshCertificateHelpFunc(_ map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries create vault-ssh-certificate -credential-store-id [options] [args]",
"",
" Create a vault-ssh-certificate-type credential library. Example:",
"",
` $ boundary credential-libraries create vault-ssh-certificate -credential-store-id csvlt_1234567890 -vault-path "/ssh/sign/role" -username user`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credential-libraries update vault-ssh-certificate [options] [args]",
"",
" Update a vault-ssh-certificate-type credential library given its ID. Example:",
"",
` $ boundary credential-libraries update vault-ssh-certificate -id clvsclt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
@@ -203,7 +203,7 @@ func (c *VaultCommand) Run(args []string) int {
switch c.Func {
case "create":
createResult, err = credentiallibrariesClient.Create(c.Context, c.FlagCredentialStoreId, opts...)
createResult, err = credentiallibrariesClient.Create(c.Context, "vault", c.FlagCredentialStoreId, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
@@ -12,39 +12,10 @@ func init() {
extraVaultFlagsHandlingFunc = extraVaultFlagHandlingFuncImpl
}
const (
pathFlagName = "vault-path"
httpMethodFlagName = "vault-http-method"
httpRequestBodyFlagName = "vault-http-request-body"
credentialTypeFlagName = "credential-type"
credentialMappingFlagName = "credential-mapping-override"
)
type extraVaultCmdVars struct {
flagPath string
flagHttpMethod string
flagHttpRequestBody string
flagCredentialType string
flagCredentialMapping []base.CombinedSliceFlagValue
}
type extraVaultCmdVars extraVaultGenericCmdVars
func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialTypeFlagName,
credentialMappingFlagName,
},
"update": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialMappingFlagName,
},
}
return flags
return extraVaultGenericActionsFlagsMapFuncImpl()
}
func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSet) {
@@ -150,6 +121,7 @@ func (c *VaultCommand) extraVaultHelpFunc(_ map[string]func() string) string {
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Deprecated: use 'boundary credential-libraries create vault-generic' instead.",
"Usage: boundary credential-libraries create vault -credential-store-id [options] [args]",
"",
" Create a vault-type credential library. Example:",
@@ -161,6 +133,7 @@ func (c *VaultCommand) extraVaultHelpFunc(_ map[string]func() string) string {
case "update":
helpStr = base.WrapForHelpText([]string{
"Deprecated: use 'boundary credential-libraries update vault-generic' instead.",
"Usage: boundary credential-libraries update vault [options] [args]",
"",
" Update a vault-type credential library given its ID. Example:",
+6 -1
View File
@@ -139,6 +139,11 @@ type CombinedSliceFlagValuePopulationInput struct {
// combination method, e.g. "attr" will be used to build "string-attr"; also
// used for generating help texts
PiecewisePopulationInputBaseName string
// If ProtoCompat is true, the key will be validated against proto3 syntax
// requirements for identifiers. If the string is split via KeyDelimiter, each
// segment will be evaluated independently.
PiecewiseNoProtoCompat bool
}
func PopulateCombinedSliceFlagValue(input CombinedSliceFlagValuePopulationInput) {
@@ -163,7 +168,7 @@ func PopulateCombinedSliceFlagValue(input CombinedSliceFlagValuePopulationInput)
Target: input.PiecewisePopulationFlag,
KvSplit: true,
KeyDelimiter: &keyDelimiter,
ProtoCompatKey: true,
ProtoCompatKey: !input.PiecewiseNoProtoCompat,
KeyOnlyAllowed: true,
Usage: fmt.Sprintf(
"A key=value pair to add to the request's %s map. "+
+45 -12
View File
@@ -227,18 +227,51 @@ var inputStructs = map[string][]*cmdInfo{
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"},
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
NeedsSubtypeInCreate: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
{
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault-generic",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
NeedsSubtypeInCreate: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
{
ResourceType: resource.CredentialLibrary.String(),
Pkg: "credentiallibraries",
StdActions: []string{"create", "update"},
SubActionPrefix: "vault-ssh-certificate",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
NeedsSubtypeInCreate: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
},
+8
View File
@@ -30,6 +30,7 @@ const (
UnspecifiedType Type = "unspecified"
UsernamePasswordType Type = "username_password"
SshPrivateKeyType Type = "ssh_private_key"
SshCertificateType Type = "ssh_certificate"
JsonType Type = "json"
)
@@ -97,6 +98,13 @@ type Request struct {
Purpose Purpose
}
// SshCertificate is a credential containing a client certificate, username,
// and SSH private key.
type SshCertificate interface {
SshPrivateKey
Certificate() []byte
}
// Issuer issues dynamic credentials.
type Issuer interface {
// Issue issues dynamic credentials for a session from the requested
+1 -39
View File
@@ -1,10 +1,8 @@
package vault
import (
"database/sql"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db/sanitize"
"github.com/hashicorp/boundary/internal/db/sentinel"
@@ -46,14 +44,11 @@ type Credential struct {
expiration time.Duration `gorm:"-"`
}
func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) {
func newCredential(libraryId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) {
const op = "vault.newCredential"
if libraryId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no library id")
}
if sessionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no session id")
}
if len(tokenHmac) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no tokenHmac")
}
@@ -69,7 +64,6 @@ func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, ex
expiration: expiration.Round(time.Second),
Credential: &store.Credential{
LibraryId: libraryId,
SessionId: sessionId,
ExternalId: externalId,
TokenHmac: tokenHmac,
Status: status,
@@ -117,38 +111,6 @@ func (c *Credential) oplog(op oplog.OpType) oplog.Metadata {
return metadata
}
func (c *Credential) insertQuery() (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", c.SessionId),
sql.Named("token_hmac", c.TokenHmac),
sql.Named("external_id", c.ExternalId),
sql.Named("is_renewable", c.IsRenewable),
sql.Named("status", c.Status),
sql.Named("last_renewal_time", "now()"),
}
switch {
case c.expiration == 0:
query = insertCredentialWithInfiniteExpirationQuery
default:
query = insertCredentialWithExpirationQuery
queryValues = append(queryValues, sql.Named("expiration_time", int(c.expiration.Round(time.Second).Seconds())))
}
return
}
func (c *Credential) updateSessionQuery(purpose credential.Purpose) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", c.SessionId),
sql.Named("purpose", string(purpose)),
}
query = updateSessionCredentialQuery
return
}
func (c *Credential) updateExpirationQuery() (query string, queryValues []any) {
queryValues = []any{
int(c.expiration.Round(time.Second).Seconds()),
+3 -13
View File
@@ -56,17 +56,6 @@ func TestCredential_New(t *testing.T) {
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{
@@ -144,7 +133,7 @@ func TestCredential_New(t *testing.T) {
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,
got, err := newCredential(tt.args.libraryId,
tt.args.externalId, tt.args.tokenHmac, tt.args.expiration)
if tt.wantErr {
assert.Error(err)
@@ -162,7 +151,8 @@ func TestCredential_New(t *testing.T) {
tt.want.PublicId = id
got.PublicId = id
query, queryValues := got.insertQuery()
query, queryValues := insertQuery(got, tt.args.sessionId)
require.NoError(err)
rows, err2 := rw.Exec(ctx, query, queryValues)
assert.Equal(1, rows)
+12
View File
@@ -9,6 +9,18 @@ const (
httpMethodField = "HttpMethod"
httpRequestBodyField = "HttpRequestBody"
usernameField = "Username"
keyTypeField = "KeyType"
keyBitsField = "KeyBits"
ttlField = "Ttl"
keyIdField = "KeyId"
// CriticalOptionsField represents the field mask indicating a critical option
// update has been requested.
CriticalOptionsField = "CriticalOptions"
// ExtensionsField represents the field mask indicating an extension
// update has been requested.
ExtensionsField = "Extensions"
certificateField = "Certificate"
certificateKeyField = "CertificateKey"
vaultAddressField = "VaultAddress"
+52
View File
@@ -34,6 +34,13 @@ type options struct {
withOverridePrivateKeyAttribute string
withOverridePrivateKeyPassphraseAttribute string
withMappingOverride MappingOverride
withKeyType string
withKeyBits uint32
withTtl string
withKeyId string
withCriticalOptions string
withExtensions string
}
func getDefaultOptions() options {
@@ -174,3 +181,48 @@ func WithMappingOverride(m MappingOverride) Option {
o.withMappingOverride = m
}
}
// WithKeyType provides an optional ssh private key type to use
// with a ssh certificate credential library. Must be rsa, ed25519, or ecdsa.
func WithKeyType(t string) Option {
return func(o *options) {
o.withKeyType = t
}
}
// WithKeyBits provides an optional number of bits used to generate an ssh private key.
func WithKeyBits(b uint32) Option {
return func(o *options) {
o.withKeyBits = b
}
}
// WithTtl provides an optional requested time to live for a generated ssh certificate.
func WithTtl(t string) Option {
return func(o *options) {
o.withTtl = t
}
}
// WithKeyId provides an optional key id for a created certificate.
func WithKeyId(i string) Option {
return func(o *options) {
o.withKeyId = i
}
}
// WithCriticalOptions provides an optional map of the critical options
// that the certificate should be signed for.
func WithCriticalOptions(s string) Option {
return func(o *options) {
o.withCriticalOptions = s
}
}
// WithExtensions provides a optional map of the extensions
// that the certificate should be signed for.
func WithExtensions(s string) Option {
return func(o *options) {
o.withExtensions = s
}
}
+588 -64
View File
@@ -2,14 +2,25 @@ package vault
import (
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"database/sql"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"regexp"
"strings"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/internal/sshprivatekey"
"github.com/hashicorp/boundary/internal/credential/vault/internal/usernamepassword"
"github.com/hashicorp/boundary/internal/db/sentinel"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
@@ -17,6 +28,8 @@ import (
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
vault "github.com/hashicorp/vault/api"
"github.com/mikesmitty/edkey"
"golang.org/x/crypto/ssh"
"google.golang.org/protobuf/proto"
)
@@ -25,14 +38,16 @@ var _ credential.Dynamic = (*baseCred)(nil)
type baseCred struct {
*Credential
lib *issueCredentialLibrary
lib issuingCredentialLibrary
secretData map[string]any
}
func (bc *baseCred) Secret() credential.SecretData { return bc.secretData }
func (bc *baseCred) Library() credential.Library { return bc.lib }
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.Purpose }
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.GetPurpose() }
func (bc *baseCred) getExpiration() time.Duration { return bc.expiration }
func (bc *baseCred) getCredential() *Credential { return bc.Credential }
func (bc *baseCred) isRevokable() bool { return bc.ExternalId != sentinel.ExternalIdNone }
// convert converts bc to a specific credential type if bc is not
// UnspecifiedType.
@@ -67,7 +82,12 @@ func baseToUsrPass(ctx context.Context, bc *baseCred) (*usrPassCred, error) {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
}
uAttr, pAttr := bc.lib.UsernameAttribute, bc.lib.PasswordAttribute
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
if !ok {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
}
uAttr, pAttr := lib.UsernameAttribute, lib.PasswordAttribute
if uAttr == "" {
uAttr = "username"
}
@@ -109,7 +129,12 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
}
uAttr, pkAttr, pAttr := bc.lib.UsernameAttribute, bc.lib.PrivateKeyAttribute, bc.lib.PrivateKeyPassphraseAttribute
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
if !ok {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
}
uAttr, pkAttr, pAttr := lib.UsernameAttribute, lib.PrivateKeyAttribute, lib.PrivateKeyPassphraseAttribute
if uAttr == "" {
uAttr = "username"
}
@@ -132,12 +157,25 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
}, nil
}
var _ credential.Library = (*issueCredentialLibrary)(nil)
type sshCertCred struct {
*sshPrivateKeyCred
certificate []byte
}
// A issueCredentialLibrary contains all the values needed to connect to Vault and
func (c *sshCertCred) Certificate() []byte { return c.certificate }
var _ credential.Library = (*genericIssuingCredentialLibrary)(nil)
type issuingCredentialLibrary interface {
credential.Library
GetPurpose() credential.Purpose
retrieveCredential(context.Context, errors.Op, ...credential.Option) (dynamicCred, error)
}
// A genericIssuingCredentialLibrary contains all the values needed to connect to Vault and
// retrieve credentials.
type issueCredentialLibrary struct {
PublicId string `gorm:"primary_key"`
type genericIssuingCredentialLibrary struct {
PublicId string
StoreId string
Name string
Description string
@@ -147,7 +185,7 @@ type issueCredentialLibrary struct {
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredType string `gorm:"column:credential_type"`
CredType string
ProjectId string
VaultAddress string
Namespace string
@@ -167,13 +205,13 @@ type issueCredentialLibrary struct {
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
Purpose credential.Purpose
}
func (pl *issueCredentialLibrary) clone() *issueCredentialLibrary {
func (pl *genericIssuingCredentialLibrary) clone() *genericIssuingCredentialLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &issueCredentialLibrary{
return &genericIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
@@ -208,15 +246,16 @@ func (pl *issueCredentialLibrary) clone() *issueCredentialLibrary {
}
}
func (pl *issueCredentialLibrary) GetPublicId() string { return pl.PublicId }
func (pl *issueCredentialLibrary) GetStoreId() string { return pl.StoreId }
func (pl *issueCredentialLibrary) GetName() string { return pl.Name }
func (pl *issueCredentialLibrary) GetDescription() string { return pl.Description }
func (pl *issueCredentialLibrary) GetVersion() uint32 { return pl.Version }
func (pl *issueCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *issueCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *genericIssuingCredentialLibrary) GetPublicId() string { return pl.PublicId }
func (pl *genericIssuingCredentialLibrary) GetStoreId() string { return pl.StoreId }
func (pl *genericIssuingCredentialLibrary) GetName() string { return pl.Name }
func (pl *genericIssuingCredentialLibrary) GetDescription() string { return pl.Description }
func (pl *genericIssuingCredentialLibrary) GetVersion() uint32 { return pl.Version }
func (pl *genericIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *genericIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *genericIssuingCredentialLibrary) GetPurpose() credential.Purpose { return pl.Purpose }
func (pl *issueCredentialLibrary) CredentialType() credential.Type {
func (pl *genericIssuingCredentialLibrary) CredentialType() credential.Type {
switch ct := pl.CredType; ct {
case "":
return credential.UnspecifiedType
@@ -225,41 +264,8 @@ func (pl *issueCredentialLibrary) CredentialType() credential.Type {
}
}
func (pl *issueCredentialLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(issueCredentialLibrary).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(ctx, 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(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *issueCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(issueCredentialLibrary).client"
func (pl *genericIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(genericIssuingCredentialLibrary).client"
clientConfig := &clientConfig{
Addr: pl.VaultAddress,
Token: pl.Token,
@@ -284,15 +290,15 @@ func (pl *issueCredentialLibrary) client(ctx context.Context) (vaultClient, erro
type dynamicCred interface {
credential.Dynamic
getExpiration() time.Duration
insertQuery() (query string, queryValues []any)
updateSessionQuery(purpose credential.Purpose) (query string, queryValues []any)
getCredential() *Credential
isRevokable() bool
}
// retrieveCredential retrieves a dynamic credential from Vault for the
// given sessionId.
//
// Supported options: credential.WithTemplateData
func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, sessionId string, opt ...credential.Option) (dynamicCred, error) {
func (pl *genericIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, opt ...credential.Option) (dynamicCred, error) {
opts, err := credential.GetOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
@@ -358,7 +364,7 @@ func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op err
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
cred, err := newCredential(pl.GetPublicId(), sessionId, secret.LeaseID, pl.TokenHmac, leaseDuration)
cred, err := newCredential(pl.GetPublicId(), secret.LeaseID, pl.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
@@ -374,11 +380,11 @@ func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op err
}
// TableName returns the table name for gorm.
func (pl *issueCredentialLibrary) TableName() string {
func (pl *genericIssuingCredentialLibrary) TableName() string {
return "credential_vault_library_issue_credentials"
}
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]*issueCredentialLibrary, error) {
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]issuingCredentialLibrary, error) {
const op = "vault.(Repository).getIssueCredLibraries"
mapper, err := newMapper(requests)
@@ -405,9 +411,9 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
}
defer rows.Close()
var libs []*issueCredentialLibrary
var libs []*privateCredentialLibraryAllTypes
for rows.Next() {
var lib issueCredentialLibrary
var lib privateCredentialLibraryAllTypes
if err := r.reader.ScanRows(ctx, rows, &lib); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
}
@@ -422,6 +428,7 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
}
}
var decryptedLibs []issuingCredentialLibrary
for _, pl := range libs {
databaseWrapper, err := r.kms.GetWrapper(ctx, pl.ProjectId, kms.KeyPurposeDatabase)
if err != nil {
@@ -431,9 +438,209 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
if err := pl.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
decryptedLibs = append(decryptedLibs, pl.toTypedIssuingCredentialLibrary())
}
return libs, nil
return decryptedLibs, nil
}
// privateCredentialLibraryAllTypes is a clone of genericIssuingCredentialLibrary. Contains
// all the values needed to connect to Vault and retrieve credentials.
type privateCredentialLibraryAllTypes struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredType string `gorm:"column:credential_type"`
ProjectId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
Token TokenSecret
CtToken []byte
TokenHmac []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
UsernameAttribute string
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
KeyType string
KeyBits int
Username string
Ttl string
KeyId string
CriticalOptions []byte
Extensions []byte
CredLibType string
}
func (pl *privateCredentialLibraryAllTypes) GetPublicId() string { return pl.PublicId }
func (pl *privateCredentialLibraryAllTypes) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateCredentialLibraryAllTypes).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(ctx, 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(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *privateCredentialLibraryAllTypes) clone() *privateCredentialLibraryAllTypes {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateCredentialLibraryAllTypes{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ProjectId: pl.ProjectId,
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,
WorkerFilter: pl.WorkerFilter,
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,
KeyType: pl.KeyType,
KeyBits: pl.KeyBits,
Username: pl.Username,
Ttl: pl.Ttl,
KeyId: pl.KeyId,
CriticalOptions: pl.CriticalOptions,
Extensions: pl.Extensions,
CredLibType: pl.CredLibType,
}
}
func (pl *privateCredentialLibraryAllTypes) toTypedIssuingCredentialLibrary() issuingCredentialLibrary {
switch pl.CredLibType {
case "ssh-signed-cert":
return &sshCertIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
Username: pl.Username,
Name: pl.Name,
Description: pl.Description,
CreateTime: pl.CreateTime,
UpdateTime: pl.UpdateTime,
Version: pl.Version,
ProjectId: pl.ProjectId,
VaultPath: pl.VaultPath,
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
WorkerFilter: pl.WorkerFilter,
TokenHmac: pl.TokenHmac,
Token: pl.Token,
CtToken: pl.CtToken,
TokenKeyId: pl.TokenKeyId,
ClientCert: pl.ClientCert,
ClientKey: pl.ClientKey,
CtClientKey: pl.CtClientKey,
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
KeyType: pl.KeyType,
KeyBits: pl.KeyBits,
Ttl: pl.Ttl,
KeyId: pl.KeyId,
CriticalOptions: pl.CriticalOptions,
Extensions: pl.Extensions,
}
default:
return &genericIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
Description: pl.Description,
CreateTime: pl.CreateTime,
UpdateTime: pl.UpdateTime,
Version: pl.Version,
ProjectId: pl.ProjectId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: pl.HttpRequestBody,
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
WorkerFilter: pl.WorkerFilter,
TokenHmac: pl.TokenHmac,
Token: pl.Token,
CtToken: pl.CtToken,
TokenKeyId: pl.TokenKeyId,
ClientCert: pl.ClientCert,
ClientKey: pl.ClientKey,
CtClientKey: pl.CtClientKey,
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
}
// requestMap takes a slice of credential requests and provides of list of
@@ -478,3 +685,320 @@ func (m *requestMap) libIds() []string {
func (m *requestMap) get(libraryId string) []credential.Purpose {
return m.ids[libraryId]
}
type sshCertIssuingCredentialLibrary struct {
PublicId string
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultPath string
CredType string
ProjectId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
Token TokenSecret
CtToken []byte
TokenHmac []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Username string
KeyType string
KeyBits int
KeyId string
Ttl string
CriticalOptions []byte
Extensions []byte
Purpose credential.Purpose
}
func (lib *sshCertIssuingCredentialLibrary) GetPublicId() string { return lib.PublicId }
func (lib *sshCertIssuingCredentialLibrary) GetStoreId() string { return lib.StoreId }
func (lib *sshCertIssuingCredentialLibrary) GetName() string { return lib.Name }
func (lib *sshCertIssuingCredentialLibrary) GetDescription() string { return lib.Description }
func (lib *sshCertIssuingCredentialLibrary) GetVersion() uint32 { return lib.Version }
func (lib *sshCertIssuingCredentialLibrary) GetPurpose() credential.Purpose { return lib.Purpose }
func (lib *sshCertIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp {
return lib.CreateTime
}
func (lib *sshCertIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp {
return lib.UpdateTime
}
func (lib *sshCertIssuingCredentialLibrary) CredentialType() credential.Type {
switch ct := lib.CredType; ct {
case "":
return credential.UnspecifiedType
default:
return credential.Type(ct)
}
}
func (lib *sshCertIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(genericIssuingCredentialLibrary).client"
clientConfig := &clientConfig{
Addr: lib.VaultAddress,
Token: lib.Token,
CaCert: lib.CaCert,
TlsServerName: lib.TlsServerName,
TlsSkipVerify: lib.TlsSkipVerify,
Namespace: lib.Namespace,
}
if lib.ClientKey != nil {
clientConfig.ClientCert = lib.ClientCert
clientConfig.ClientKey = lib.ClientKey
}
client, err := vaultClientFactoryFn(ctx, clientConfig, WithWorkerFilter(lib.WorkerFilter))
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
func generatePublicPrivateKeys(ctx context.Context, keyType string, keyBits int) (string, []byte, error) {
const op = "vault.generatePublicPrivateKeys"
pemBlock := pem.Block{}
var sshKey ssh.PublicKey
switch keyType {
case KeyTypeRsa:
pemBlock.Type = "RSA PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
key, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
if sshKey, err = ssh.NewPublicKey(&key.PublicKey); err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
pemBlock.Bytes = x509.MarshalPKCS1PrivateKey(key)
case KeyTypeEd25519:
pemBlock.Type = "OPENSSH PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
if sshKey, err = ssh.NewPublicKey(pubKey); err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
if pemBlock.Bytes = edkey.MarshalED25519PrivateKey(privKey); pemBlock.Bytes == nil {
return "", nil, errors.New(ctx, errors.Encode, op, "failed to marshal ed25519 private key")
}
case KeyTypeEcdsa:
pemBlock.Type = "EC PRIVATE KEY" // these values are copied from the crypto ssh library in ssh/keys.go
var curve elliptic.Curve
switch keyBits {
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 521:
curve = elliptic.P521()
default:
return "", nil, errors.New(ctx, errors.InvalidParameter, op, "invalid KeyBits. when KeyType=ecdsa, KeyBits must be one of: 256, 384, or 521")
}
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
if sshKey, err = ssh.NewPublicKey(&key.PublicKey); err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
if pemBlock.Bytes, err = x509.MarshalECPrivateKey(key); err != nil {
return "", nil, errors.Wrap(ctx, err, op)
}
default:
return "", nil, errors.New(ctx, errors.InvalidParameter, op, "invalid KeyType, must be one of: \"rsa\", \"ed25519\", or \"ecdsa\"")
}
publicKey := base64.StdEncoding.EncodeToString(sshKey.Marshal())
privateKey := pem.EncodeToMemory(&pemBlock)
if privateKey == nil {
return "", nil, errors.New(ctx, errors.Encode, op, "failed to encode private key to PEM format")
}
return publicKey, privateKey, nil
}
type sshCertVaultBody struct {
KeyType string `json:"key_type,omitempty"` // must be "rsa", "ed25519", or "ecdsa"
KeyBits int `json:"key_bits,omitempty"` // with key_type=rsa, allowed values are: 2048 (default), 3072, or 4096; with key_type=ecdsa, allowed values are: 256 (default), 384, or 521; ignored with key_type=ed25519
PublicKey string `json:"public_key,omitempty"`
TTL string `json:"ttl,omitempty"`
ValidPrincipals string `json:"valid_principals,omitempty"` // this needs to be "generated" off of the username provided in config
CertType string `json:"cert_type,omitempty"` // this should always be "user"
KeyId string `json:"key_id,omitempty"` // this will be loaded directly from lib
CriticalOptions map[string]string `json:"critical_options,omitempty"` // this will be loaded directly from lib
Extensions map[string]string `json:"extensions,omitempty"` // this will be loaded directly from lib
}
var vaultPathRegexp = regexp.MustCompile(`^.+\/(sign|issue)\/[^\/\\\s]+$`)
// retrieveCredential retrieves a dynamic connection credential from Vault
// for a specific session and connection.
//
// Supported options: credential.WithTemplateData
func (lib *sshCertIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, opt ...credential.Option) (dynamicCred, error) {
opts, err := credential.GetOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
// Get the credential ID early. No need to get a secret from Vault
// if there is no way to save it in the database.
credId, err := newCredentialId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
client, err := lib.client(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
// build as much of the payload as we can, since sign/issue share many attributes
// Template the username
tplate, err := template.New(ctx, lib.Username)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
username, err := tplate.Generate(ctx, opts.WithTemplateData)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var criticalOptions map[string]string
if lib.CriticalOptions != nil {
if json.Unmarshal(lib.CriticalOptions, &criticalOptions) != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
var extensions map[string]string
if lib.Extensions != nil {
if json.Unmarshal(lib.Extensions, &extensions) != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
payload := sshCertVaultBody{
ValidPrincipals: username,
CertType: "user",
CriticalOptions: criticalOptions,
Extensions: extensions,
TTL: lib.Ttl,
KeyId: lib.KeyId,
}
var privateKey credential.PrivateKey
var secret *vault.Secret
match := vaultPathRegexp.FindStringSubmatch(lib.VaultPath)
if len(match) < 2 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "vault path was not in an expected format. expected path containing \"sign\" or \"issue\"")
}
// by definition, if match exists, then match[1] == "sign" or "issue"
switch match[1] {
case "sign":
payload.PublicKey, privateKey, err = generatePublicPrivateKeys(ctx, lib.KeyType, lib.KeyBits)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
body, err := json.Marshal(payload)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
secret, err = client.post(ctx, lib.VaultPath, body)
if err != nil {
// TODO(mgaffney) 05/2021: detect if the error is because of an
// expired or invalid token
return nil, errors.Wrap(ctx, err, op)
}
if secret == nil {
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
}
case "issue":
payload.KeyBits = lib.KeyBits
if lib.KeyType == KeyTypeEcdsa {
// this is a special case where internal to boundary, we refer to the crypto
// library name, but vault refers to it simply as "ec" for "Elliptic Curve"
payload.KeyType = "ec"
} else {
payload.KeyType = lib.KeyType
}
body, err := json.Marshal(payload)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
secret, err = client.post(ctx, lib.VaultPath, body)
if err != nil {
// TODO(mgaffney) 05/2021: detect if the error is because of an
// expired or invalid token
return nil, errors.Wrap(ctx, err, op)
}
if secret == nil {
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
}
pk, ok := secret.Data["private_key"].(string)
if !ok {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a private key or response was not in the expected format")
}
privateKey = []byte(pk)
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
cred, err := newCredential(lib.GetPublicId(), secret.LeaseID, lib.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cred.PublicId = credId
cred.IsRenewable = secret.Renewable // possibly set to just false
// same location for both
cert, ok := secret.Data["signed_key"].(string)
if !ok {
return nil, errors.New(ctx, errors.VaultInvalidCredentialMapping, op, "vault secret did not contain a signed key or response was not in the expected format")
}
return &sshCertCred{
sshPrivateKeyCred: &sshPrivateKeyCred{
baseCred: &baseCred{
Credential: cred,
lib: lib,
secretData: secret.Data,
},
username: username,
privateKey: privateKey,
},
certificate: []byte(cert),
}, nil
}
+221 -46
View File
@@ -2,7 +2,9 @@ package vault
import (
"context"
"fmt"
"testing"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
@@ -10,6 +12,8 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/boundary/internal/util/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -63,7 +67,7 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
opts = append(opts, WithClientCert(clientCert))
}
_, token := v.CreateToken(t)
_, token := v.CreateToken(t, WithTokenPeriod(time.Hour))
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...)
assert.NoError(err)
@@ -269,7 +273,10 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
require.NotNil(gotLibs)
assert.Len(gotLibs, len(libs))
for _, got := range gotLibs {
for _, gotLib := range gotLibs {
got, ok := gotLib.(*genericIssuingCredentialLibrary)
require.True(ok)
assert.Equal(TokenSecret(token), got.Token)
if tt.tls == TestClientTLS {
require.NotNil(got.ClientKey)
@@ -414,7 +421,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "library-not-username-password-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -423,7 +430,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-username-default-password-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -435,7 +442,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-password-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -447,7 +454,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -463,7 +470,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -483,7 +490,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-username-override-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -502,7 +509,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-username-default-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -521,7 +528,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-username",
},
@@ -537,7 +544,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-password-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-password",
},
@@ -553,7 +560,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -568,7 +575,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -580,7 +587,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-username-default-password-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -595,7 +602,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-passsword-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -610,7 +617,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -626,7 +633,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -639,7 +646,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -656,7 +663,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -675,7 +682,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -698,7 +705,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-username-override-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -720,7 +727,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-username-default-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -779,7 +786,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "library-not-ssh-private-key-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -788,7 +795,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-username-default-pk-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -800,7 +807,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-pk-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -812,7 +819,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -828,7 +835,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -846,7 +853,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -866,7 +873,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -890,7 +897,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-username-override-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -909,7 +916,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-username-default-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
@@ -928,7 +935,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-username",
},
@@ -944,7 +951,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-pk-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-pk",
},
@@ -960,7 +967,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -975,7 +982,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -987,7 +994,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-username-default-pk-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1002,7 +1009,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-pk-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1017,7 +1024,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1033,7 +1040,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-data-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1046,7 +1053,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1063,7 +1070,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1082,7 +1089,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1103,7 +1110,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1126,7 +1133,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1153,7 +1160,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-username-override-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -1175,7 +1182,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-username-default-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
@@ -1212,3 +1219,171 @@ func TestBaseToSshPriKey(t *testing.T) {
})
}
}
func TestRepository_sshCertIssuingCredentialLibrary_retrieveCredential(t *testing.T) {
t.Parallel()
// create test vault server
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS), WithVaultVersion("1.12.2"))
require.NotNil(t, v)
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
assert.NoError(t, err)
require.NotEmpty(t, mounts)
beforeCount := len(mounts)
// enable ssh secrets engine
v.MountSSH(t, WithAllowedExtension("permit-pty"))
mounts, err = vc.Sys().ListMounts()
fmt.Printf("mounts: %#v\n", mounts)
assert.NoError(t, err)
require.NotEmpty(t, mounts)
afterCount := len(mounts)
assert.Greater(t, afterCount, beforeCount)
// create and setup db
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
sec, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "ssh"}), WithTokenPeriod(time.Hour))
sche := scheduler.TestScheduler(t, conn, wrapper)
kms := kms.TestKms(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
require.NoError(t, err)
require.NotNil(t, repo)
cs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), v.Addr, token, sec.Auth.Accessor)
tests := []struct {
name string
username string
vaulthPath string
opts []Option
retOpts []credential.Option
expected map[string]string
}{
{
name: "vault sign boundary ed25519 key",
username: "username",
vaulthPath: "ssh/sign/boundary",
opts: []Option{WithKeyType(KeyTypeEd25519)},
},
{
name: "vault sign boundary rsa(4096) key",
username: "username-2-electric-boogaloo",
vaulthPath: "ssh/sign/boundary",
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(4096)},
},
{
name: "vault sign boundary ec(521) key",
username: "username-3-the-namening",
vaulthPath: "ssh/sign/boundary",
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(521)},
},
{
name: "vault issue ed25519 cert",
username: "username-4-revengeance",
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeEd25519)},
},
{
name: "vault issue rsa(4096) cert",
username: "username-5-this-time-its-personal",
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(4096)},
},
{
name: "vault issue ec(521) cert",
username: "username-6-the-holiday-episode",
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(521)},
},
{
name: "vault issue rsa(2048) cert with critical options and extensions",
username: "username-7-because-789",
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(2048), WithCriticalOptions("{ \"force-commnad\": \"/bin/some-script\" }"), WithExtensions("{ \"permit-pty\": \"\" }")},
},
{
name: "vault sign boundary rsa(3072) key with critical options and extensions",
username: "username-7-because-789",
vaulthPath: "ssh/sign/boundary",
opts: []Option{WithKeyType(KeyTypeRsa), WithKeyBits(3072), WithCriticalOptions("{ \"force-commnad\": \"/bin/some-script\" }"), WithExtensions("{ \"permit-pty\": \"\" }")},
},
{
name: "vault issue ec(256) cert with template username",
username: "username-8-{{ .User.Name }}",
expected: map[string]string{
"username": "username-8-revenge-of-the-template",
},
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(256)},
retOpts: []credential.Option{credential.WithTemplateData(template.Data{User: template.User{Name: util.Pointer("revenge-of-the-template")}})},
},
{
name: "vault issue ec(384) cert with disallowed extension",
username: "username-10-because-789",
expected: map[string]string{
"error": "extensions [permit-port-forwarding] are not on allowed list",
},
vaulthPath: "ssh/issue/boundary",
opts: []Option{WithKeyType(KeyTypeEcdsa), WithKeyBits(384), WithExtensions("{ \"permit-port-forwarding\": \"\" }")},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
// create ssh library
lib, err := NewSSHCertificateCredentialLibrary(cs.GetPublicId(), tt.vaulthPath, tt.username, tt.opts...)
require.NoError(err)
require.NotNil(lib)
lib.PublicId, err = newSSHCertificateCredentialLibraryId()
require.NoError(err)
_, err = rw.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
return iw.Create(ctx, lib)
},
)
require.NoError(err)
req := credential.Request{
SourceId: lib.GetPublicId(),
Purpose: "doesn't matter",
}
libs, err := repo.getIssueCredLibraries(ctx, []credential.Request{req})
require.NoError(err)
require.NotEmpty(libs)
require.Equal(1, len(libs))
cred, err := libs[0].retrieveCredential(ctx, "op", tt.retOpts...)
if retErr, ok := tt.expected["error"]; ok {
require.ErrorContains(err, retErr)
return // retrieveCredential failed (as expected) don't do the rest of the checks
}
require.NoError(err)
cert, ok := cred.(*sshCertCred)
require.True(ok)
if usr, ok := tt.expected["username"]; ok {
require.Equal(usr, cert.username)
}
// TODO: somehow check that the ssh cert matches the private key
// for now, manually check that private key and cert match and that sign/issue both return correct formats
fmt.Printf("test: %s\npriv:\n%s\ncert:\n%s\n", tt.name, string(cert.privateKey), string(cert.certificate))
// ensure credential matches expected SshCertificate
_, ok = cred.(credential.SshCertificate)
require.True(ok)
})
}
}
+22 -5
View File
@@ -8,18 +8,27 @@ import (
)
func init() {
if err := subtypes.Register(credential.Domain, Subtype, CredentialStorePrefix, CredentialLibraryPrefix, DynamicCredentialPrefix); err != nil {
if err := subtypes.Register(credential.Domain, Subtype, CredentialStorePrefix, DynamicCredentialPrefix); err != nil {
panic(err)
}
if err := subtypes.Register(credential.Domain, GenericLibrarySubtype, CredentialLibraryPrefix); err != nil {
panic(err)
}
if err := subtypes.Register(credential.Domain, SSHCertificateLibrarySubtype, SSHCertificateCredentialLibraryPrefix); err != nil {
panic(err)
}
}
// PublicId prefixes for the resources in the vault package.
const (
CredentialStorePrefix = "csvlt"
CredentialLibraryPrefix = "clvlt"
DynamicCredentialPrefix = "cdvlt"
CredentialStorePrefix = "csvlt"
CredentialLibraryPrefix = "clvlt"
DynamicCredentialPrefix = "cdvlt"
SSHCertificateCredentialLibraryPrefix = "clvsclt"
Subtype = subtypes.Subtype("vault")
Subtype = subtypes.Subtype("vault")
GenericLibrarySubtype = subtypes.Subtype("vault-generic")
SSHCertificateLibrarySubtype = subtypes.Subtype("vault-ssh-certificate")
)
func newCredentialStoreId() (string, error) {
@@ -45,3 +54,11 @@ func newCredentialLibraryId() (string, error) {
}
return id, nil
}
func newSSHCertificateCredentialLibraryId() (string, error) {
id, err := db.NewPublicId(SSHCertificateCredentialLibraryPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, "vault.newSSHCertificateCredentialLibraryPrefix")
}
return id, nil
}
@@ -2,6 +2,7 @@ package vault
import (
"context"
"database/sql"
"fmt"
"time"
@@ -13,6 +14,38 @@ import (
var _ credential.Issuer = (*Repository)(nil)
func insertQuery(c *Credential, sessionId string) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", sessionId),
sql.Named("token_hmac", c.TokenHmac),
sql.Named("external_id", c.ExternalId),
sql.Named("is_renewable", c.IsRenewable),
sql.Named("status", c.Status),
sql.Named("last_renewal_time", "now()"),
}
switch {
case c.expiration == 0:
query = insertCredentialWithInfiniteExpirationQuery
default:
query = insertCredentialWithExpirationQuery
queryValues = append(queryValues, sql.Named("expiration_time", int(c.expiration.Round(time.Second).Seconds())))
}
return
}
func updateSessionQuery(c *Credential, sessionId string, purpose credential.Purpose) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", sessionId),
sql.Named("purpose", string(purpose)),
}
query = updateSessionCredentialQuery
return
}
// Issue issues and returns dynamic credentials from Vault for all of the
// requests and assigns them to sessionId.
func (r *Repository) Issue(ctx context.Context, sessionId string, requests []credential.Request, opt ...credential.Option) ([]credential.Dynamic, error) {
@@ -37,11 +70,17 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
var minLease time.Duration
runJobsInterval := r.scheduler.GetRunJobsInterval()
for _, lib := range libs {
cred, err := lib.retrieveCredential(ctx, op, sessionId, opt...)
cred, err := lib.retrieveCredential(ctx, op, opt...)
if err != nil {
return nil, err
}
creds = append(creds, cred)
if !cred.isRevokable() {
// No need to persist since the credential cannot be revoked nor renewed
continue
}
if cred.getExpiration() < runJobsInterval {
event.WriteError(ctx, op,
fmt.Errorf("WARNING: credential will expire before job scheduler can run"),
@@ -54,8 +93,18 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
if minLease > cred.getExpiration() {
minLease = cred.getExpiration()
}
insertQuery, insertQueryValues := cred.insertQuery()
updateQuery, updateQueryValues := cred.updateSessionQuery(lib.Purpose)
underlyingCred := cred.getCredential()
insertQuery, insertQueryValues := insertQuery(underlyingCred, sessionId)
if err != nil {
return nil, err
}
updateQuery, updateQueryValues := updateSessionQuery(underlyingCred, sessionId, cred.Purpose())
if err != nil {
return nil, err
}
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
rowsInserted, err := w.Exec(ctx, insertQuery, insertQueryValues)
@@ -80,8 +129,6 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
creds = append(creds, cred)
}
// Best effort update next run time of credential renewal job, but an error should not
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
@@ -21,6 +22,95 @@ import (
"github.com/stretchr/testify/require"
)
// since we're not using gorm tags to retrieve this, it's faster and easier to just make a new struct than import usrPassCred from the vault package
type revokableCred struct {
PublicId string
LibraryId string
SessionId string
TokenHmac []byte
ExternalId string
IsRenewable bool
Status string
LastRenewalTime *timestamp.Timestamp
ExpirationTime *timestamp.Timestamp
}
func lookupDbCred(t *testing.T, ctx context.Context, rw *db.Db, dc credential.Dynamic) *revokableCred {
rows, err := rw.Query(ctx, `
select
public_id,
library_id,
session_id,
token_hmac,
external_id,
is_renewable,
status,
last_renewal_time,
expiration_time
from credential_vault_credential
where public_id = ?;
`, []any{dc.GetPublicId()})
require.NoError(t, err)
rowCount := 0
got := revokableCred{}
for rows.Next() {
rowCount++
require.NoError(t, rows.Scan(
&got.PublicId,
&got.LibraryId,
&got.SessionId,
&got.TokenHmac,
&got.ExternalId,
&got.IsRenewable,
&got.Status,
&got.LastRenewalTime,
&got.ExpirationTime,
))
}
// Should never get more than one that matches, but can get 0
assert.LessOrEqual(t, rowCount, 1)
if rowCount == 0 {
return nil
}
return &got
}
type libT int
const (
libDB libT = iota
libUsrPassDB
libErrUsrPassDB
libPKI
libErrPKI
libKV
libErrKV
libUsrPassKV
libSshPkKV
libExpiredToken
)
type testLib struct {
PublicId string
HasLease bool
IsRenewable bool
}
type testLibMap map[libT]testLib
func (m testLibMap) GetByPublicId(id string) (testLib, bool) {
for _, v := range m {
if v.PublicId == id {
return v, true
}
}
return testLib{}, false
}
func TestRepository_IssueCredentials(t *testing.T) {
t.Parallel()
ctx := context.Background()
@@ -80,21 +170,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
assert.Equal(t, 1, num)
v.RevokeToken(t, expToken)
type libT int
const (
libDB libT = iota
libUsrPassDB
libErrUsrPassDB
libPKI
libErrPKI
libKV
libErrKV
libUsrPassKV
libSshPkKV
libExpiredToken
)
libs := make(map[libT]string)
libs := make(testLibMap)
{
libPath := path.Join("database", "creds", "opened")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath)
@@ -103,7 +179,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libDB] = lib.GetPublicId()
libs[libDB] = testLib{PublicId: lib.GetPublicId(), HasLease: true, IsRenewable: true}
}
{
libPath := path.Join("pki", "issue", "boundary")
@@ -113,7 +189,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libPKI] = lib.GetPublicId()
libs[libPKI] = testLib{PublicId: lib.GetPublicId(), HasLease: false, IsRenewable: false}
}
{
@@ -124,7 +200,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrPKI] = lib.GetPublicId()
libs[libErrPKI] = testLib{PublicId: lib.GetPublicId(), HasLease: true, IsRenewable: true}
}
{
libPath := path.Join("secret", "data", "my-up-secret")
@@ -134,7 +210,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libKV] = lib.GetPublicId()
libs[libKV] = testLib{PublicId: lib.GetPublicId(), HasLease: false, IsRenewable: true}
}
{
libPath := path.Join("secret", "data", "fake-secret")
@@ -144,7 +220,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrKV] = lib.GetPublicId()
libs[libErrKV] = testLib{PublicId: lib.GetPublicId(), HasLease: false, IsRenewable: true}
}
{
libPath := path.Join("database", "creds", "opened")
@@ -157,7 +233,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libUsrPassDB] = lib.GetPublicId()
libs[libUsrPassDB] = testLib{PublicId: lib.GetPublicId(), HasLease: true, IsRenewable: true}
}
{
libPath := path.Join("database", "creds", "opened")
@@ -174,7 +250,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrUsrPassDB] = lib.GetPublicId()
libs[libErrUsrPassDB] = testLib{PublicId: lib.GetPublicId(), HasLease: true, IsRenewable: true}
}
{
libPath := path.Join("secret", "data", "my-up-secret")
@@ -187,7 +263,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libUsrPassKV] = lib.GetPublicId()
libs[libUsrPassKV] = testLib{PublicId: lib.GetPublicId(), HasLease: false}
}
{
libPath := path.Join("secret", "data", "my-sshpk-secret")
@@ -200,7 +276,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libSshPkKV] = lib.GetPublicId()
libs[libSshPkKV] = testLib{PublicId: lib.GetPublicId(), HasLease: false}
}
{
libPath := path.Join("secret", "data", "my-up-secret")
@@ -213,7 +289,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libExpiredToken] = lib.GetPublicId()
libs[libExpiredToken] = testLib{PublicId: lib.GetPublicId(), HasLease: false}
}
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
@@ -250,7 +326,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libDB],
SourceId: libs[libDB].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -260,11 +336,11 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libDB],
SourceId: libs[libDB].PublicId,
Purpose: credential.BrokeredPurpose,
},
{
SourceId: libs[libPKI],
SourceId: libs[libPKI].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -274,7 +350,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libErrPKI],
SourceId: libs[libErrPKI].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -285,11 +361,11 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2nil,
requests: []credential.Request{
{
SourceId: libs[libDB],
SourceId: libs[libDB].PublicId,
Purpose: credential.BrokeredPurpose,
},
{
SourceId: libs[libPKI],
SourceId: libs[libPKI].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -300,7 +376,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libKV],
SourceId: libs[libKV].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -310,7 +386,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libUsrPassDB],
SourceId: libs[libUsrPassDB].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -320,7 +396,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libErrKV],
SourceId: libs[libErrKV].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -331,7 +407,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libErrUsrPassDB],
SourceId: libs[libErrUsrPassDB].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -342,7 +418,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libUsrPassKV],
SourceId: libs[libUsrPassKV].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -352,7 +428,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libSshPkKV],
SourceId: libs[libSshPkKV].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -362,7 +438,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libExpiredToken],
SourceId: libs[libExpiredToken].PublicId,
Purpose: credential.BrokeredPurpose,
},
},
@@ -406,6 +482,19 @@ func TestRepository_IssueCredentials(t *testing.T) {
assert.Fail("do not want UsernamePassword credential from library with credential type Unspecified")
}
}
if lib, ok := libs.GetByPublicId(dc.Library().GetPublicId()); ok {
retrieved := lookupDbCred(t, ctx, rw, dc)
if lib.HasLease {
// we also want to retrieve the cred from the db and make sure it's the same as the one returned by Issue
require.NotNil(retrieved)
require.Equal(retrieved.SessionId, sess.GetPublicId())
require.Equal(retrieved.PublicId, dc.GetPublicId())
assert.NotEmpty(retrieved.ExternalId)
assert.Equal(retrieved.IsRenewable, lib.IsRenewable)
} else {
assert.Nil(retrieved)
}
}
}
})
}
@@ -0,0 +1,363 @@
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/credential"
"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/go-dbw"
)
// CreateSSHCertificateCredentialLibrary inserts l into the repository and returns a new
// SSHCertificateCredentialLibrary 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) CreateSSHCertificateCredentialLibrary(ctx context.Context, projectId string, l *SSHCertificateCredentialLibrary, _ ...Option) (*SSHCertificateCredentialLibrary, error) {
const op = "vault.(Repository).CreateSSHCertificateCredentialLibrary"
if l == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil SSHCertificateCredentialLibrary")
}
if l.SSHCertificateCredentialLibrary == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded l")
}
if l.StoreId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no store id")
}
if l.VaultPath == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no vault path")
}
if l.PublicId != "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
}
if projectId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no project id")
}
if l.Username == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no username")
}
l = l.clone()
if l.KeyType == "" {
l.KeyType = KeyTypeEd25519
}
if l.KeyBits == KeyBitsDefault {
l.KeyBits = l.getDefaultKeyBits()
}
if l.GetCredentialType() == "" {
l.SSHCertificateCredentialLibrary.CredentialType = string(credential.SshCertificateType)
}
if l.GetCredentialType() != string(credential.SshCertificateType) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid credential type")
}
id, err := newSSHCertificateCredentialLibraryId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
l.setId(id)
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var newSSHCertificateCredentialLibrary *SSHCertificateCredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
var msgs []*oplog.Message
ticket, err := w.GetTicket(ctx, l)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
// insert credential library
newSSHCertificateCredentialLibrary = l.clone()
var lOplogMsg oplog.Message
if err := w.Create(ctx, newSSHCertificateCredentialLibrary, db.NewOplogMsg(&lOplogMsg)); err != nil {
return errors.Wrap(ctx, err, op)
}
msgs = append(msgs, &lOplogMsg)
metadata := l.oplog(oplog.OpType_OP_TYPE_CREATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s: name %s already exists", l.StoreId, l.Name)))
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s", l.StoreId)))
}
return newSSHCertificateCredentialLibrary, nil
}
// UpdateSSHCertificateCredentialLibrary updates the repository entry for l.PublicId with
// the values in l for the fields listed in fieldMaskPaths. It returns a
// new SSHCertificateCredentialLibrary containing the updated values and a count of the
// number of records updated. l is not changed.
//
// l must contain a valid PublicId. Name, Description, VaultPath, Username,
// KeyType, KeyBits, Ttl, KeyId, CriticalOptions, and Extensions 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.
func (r *Repository) UpdateSSHCertificateCredentialLibrary(ctx context.Context, projectId string, l *SSHCertificateCredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*SSHCertificateCredentialLibrary, int, error) {
const op = "vault.(Repository).UpdateSSHCertificateCredentialLibrary"
if l == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing SSHCertificateCredentialLibrary")
}
if l.SSHCertificateCredentialLibrary == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded SSHCertificateCredentialLibrary")
}
if l.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
if version == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
if projectId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
}
l = l.clone()
var keyTypeChange, keyBitChangeDefault bool
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(vaultPathField, f):
case strings.EqualFold(usernameField, f):
case strings.EqualFold(keyTypeField, f):
keyTypeChange = true
case strings.EqualFold(keyBitsField, f):
keyBitChangeDefault = l.KeyBits == KeyBitsDefault
case strings.EqualFold(ttlField, f):
case strings.EqualFold(keyIdField, f):
case strings.EqualFold(CriticalOptionsField, f):
case strings.EqualFold(ExtensionsField, f):
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
}
}
if keyTypeChange && l.KeyType == "" {
l.KeyType = KeyTypeEd25519
}
if keyTypeChange && keyBitChangeDefault {
l.KeyBits = l.getDefaultKeyBits()
}
origLib, err := r.LookupSSHCertificateCredentialLibrary(ctx, l.PublicId)
switch {
case err != nil:
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
case origLib == nil:
return nil, db.NoRowsAffected, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("credential library %s", l.PublicId))
}
if keyBitChangeDefault && !keyTypeChange {
l.KeyBits = origLib.getDefaultKeyBits()
}
var dbMask, nullFields []string
dbMask, nullFields = dbw.BuildUpdatePaths(
map[string]any{
nameField: l.Name,
descriptionField: l.Description,
vaultPathField: l.VaultPath,
usernameField: l.Username,
keyTypeField: l.KeyType,
keyBitsField: l.KeyBits,
ttlField: l.Ttl,
keyIdField: l.KeyId,
CriticalOptionsField: l.CriticalOptions,
ExtensionsField: l.Extensions,
},
fieldMaskPaths,
[]string{keyBitsField},
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt),
errors.WithMsg("unable to get oplog wrapper"))
}
var rowsUpdated int
var returnedCredentialLibrary *SSHCertificateCredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(rr db.Reader, w db.Writer) error {
var msgs []*oplog.Message
ticket, err := w.GetTicket(ctx, l)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
l := l.clone()
var lOplogMsg oplog.Message
// Update the credential library table
switch {
case len(dbMask) == 0 && len(nullFields) == 0:
// the credential library's fields are not being updated,
// just one of it's child objects, so we just need to
// update the library's version.
l.Version = version + 1
rowsUpdated, err = w.Update(ctx, l, []string{"Version"}, nil, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library version"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library version and %d rows updated", rowsUpdated))
}
default:
rowsUpdated, err = w.Update(ctx, l, dbMask, nullFields, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
if err != nil {
if errors.IsUniqueError(err) {
return errors.New(ctx, errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
}
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library and %d rows updated", rowsUpdated))
}
}
msgs = append(msgs, &lOplogMsg)
metadata := l.oplog(oplog.OpType_OP_TYPE_UPDATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
accl := allocSSHCertificateCredentialLibrary()
accl.PublicId = l.PublicId
if err = rr.LookupById(ctx, accl); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
}
returnedCredentialLibrary = accl
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
}
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(l.PublicId))
}
return returnedCredentialLibrary, rowsUpdated, nil
}
// LookupSSHCertificateCredentialLibrary returns the SSHCertificateCredentialLibrary for publicId.
// Returns nil, nil if no SSHCertificateCredentialLibrary is found for publicId.
func (r *Repository) LookupSSHCertificateCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*SSHCertificateCredentialLibrary, error) {
const op = "vault.(Repository).LookupCredentialLibrary"
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
l := allocSSHCertificateCredentialLibrary()
l.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, l); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
}
return l, nil
}
// ListSSHCertificateCredentialLibraries returns a slice of SSHCertificateCredentialLibraries for the
// storeId. WithLimit is the only option supported.
func (r *Repository) ListSSHCertificateCredentialLibraries(ctx context.Context, storeId string, opt ...Option) ([]*SSHCertificateCredentialLibrary, error) {
const op = "vault.(Repository).ListSSHCertificateCredentialLibraries"
if storeId == "" {
return nil, errors.New(ctx, 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 []*SSHCertificateCredentialLibrary
err := r.reader.SearchWhere(ctx, &libs, "store_id = ?", []any{storeId}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return libs, nil
}
// DeleteSSHCertificateCredentialLibrary deletes publicId from the repository and returns
// the number of records deleted.
func (r *Repository) DeleteSSHCertificateCredentialLibrary(ctx context.Context, projectId string, publicId string, _ ...Option) (int, error) {
const op = "vault.(Repository).DeleteSSHCertificateCredentialLibrary"
if publicId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
if projectId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
}
l := allocSSHCertificateCredentialLibrary()
l.PublicId = publicId
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, 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(ctx, errors.MultipleRecords, op, "more than 1 CredentialLibrary would have been deleted")
}
return err
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", l.PublicId)))
}
return rowsDeleted, nil
}
File diff suppressed because it is too large. Load diff
@@ -74,7 +74,7 @@ func TestSetupSleepyDevEnvironment(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, store)
libClient := credentiallibraries.NewClient(client)
lib, err := libClient.Create(ctx, store.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath(path.Join("database", "creds", "opened")),
lib, err := libClient.Create(ctx, store.Item.Type, store.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath(path.Join("database", "creds", "opened")),
credentiallibraries.WithVaultCredentialLibraryHttpMethod("GET"),
)
require.NoError(t, err)
@@ -0,0 +1,120 @@
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"
)
const (
KeyTypeEcdsa = "ecdsa"
KeyTypeEd25519 = "ed25519"
KeyTypeRsa = "rsa"
KeyBitsDefault = 0
KeyBitsEcdsa256 = 256
KeyBitsEcdsa384 = 384
KeyBitsEcdsa521 = 521
KeyBitsRsa2048 = 2048
KeyBitsRsa3072 = 3072
KeyBitsRsa4096 = 4096
)
// SSHCertificateCredentialLibrary is a credential library that issues
// ssh certificate using the vault ssh secret engine.
// See: https://developer.hashicorp.com/vault/api-docs/secret/ssh#sign-ssh-key
type SSHCertificateCredentialLibrary struct {
*store.SSHCertificateCredentialLibrary
tableName string `gorm:"-"`
}
// NewSSHCertificateCredentialLibrary creates a new in memory SSHCertificateCredentialLibrary
// for a Vault backend at vaultPath assigned to storeId. The SSH username field must be set.
// Name, description, key type, key bits, ttl, key id, critical options, and extensions
// are the only valid options. All other options are ignored.
func NewSSHCertificateCredentialLibrary(storeId string, vaultPath string, username string, opt ...Option) (*SSHCertificateCredentialLibrary, error) {
const op = "vault.NewSSHCertificateCredentialLibrary"
opts := getOpts(opt...)
l := &SSHCertificateCredentialLibrary{
SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{
StoreId: storeId,
Name: opts.withName,
Description: opts.withDescription,
VaultPath: vaultPath,
Username: username,
KeyType: opts.withKeyType,
KeyBits: opts.withKeyBits,
Ttl: opts.withTtl,
KeyId: opts.withKeyId,
CriticalOptions: opts.withCriticalOptions,
Extensions: opts.withExtensions,
CredentialType: string(credential.SshCertificateType),
},
}
return l, nil
}
func allocSSHCertificateCredentialLibrary() *SSHCertificateCredentialLibrary {
return &SSHCertificateCredentialLibrary{
SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{},
}
}
func (l *SSHCertificateCredentialLibrary) clone() *SSHCertificateCredentialLibrary {
cp := proto.Clone(l.SSHCertificateCredentialLibrary)
return &SSHCertificateCredentialLibrary{
SSHCertificateCredentialLibrary: cp.(*store.SSHCertificateCredentialLibrary),
}
}
func (l *SSHCertificateCredentialLibrary) setId(i string) {
l.PublicId = i
}
// TableName returns the table name.
func (l *SSHCertificateCredentialLibrary) TableName() string {
if l.tableName != "" {
return l.tableName
}
return "credential_vault_ssh_cert_library"
}
// SetTableName sets the table name.
func (l *SSHCertificateCredentialLibrary) SetTableName(n string) {
l.tableName = n
}
func (l *SSHCertificateCredentialLibrary) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{l.PublicId},
"resource-type": []string{"credential-vault-ssh-cert-library"},
"op-type": []string{op.String()},
}
if l.StoreId != "" {
metadata["store-id"] = []string{l.StoreId}
}
return metadata
}
func (l *SSHCertificateCredentialLibrary) getDefaultKeyBits() uint32 {
switch l.KeyType {
case KeyTypeEcdsa:
return KeyBitsEcdsa256
case KeyTypeRsa:
return KeyBitsRsa2048
default:
return KeyBitsDefault
}
}
// CredentialType returns the type of credential the library retrieves.
func (l *SSHCertificateCredentialLibrary) CredentialType() credential.Type {
return credential.Type(l.SSHCertificateCredentialLibrary.CredentialType)
}
var _ credential.Library = (*SSHCertificateCredentialLibrary)(nil)
+380 -102
View File
@@ -629,6 +629,210 @@ func (x *CredentialLibrary) GetCredentialType() string {
return ""
}
type SSHCertificateCredentialLibrary 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"`
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
// name is optional. If set, it must be unique within project_id.
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
// description is optional.
// @inject_tag: `gorm:"default:null"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
// store_id of the owning vault credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,6,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// vault_path is the path in Vault to request credentials from.
// It must be set.
// @inject_tag: `gorm:"not_null"`
VaultPath string `protobuf:"bytes,8,opt,name=vault_path,json=vaultPath,proto3" json:"vault_path,omitempty" gorm:"not_null"`
// username is the username to use when making an SSH connection.
// It must be set.
// @inject_tag: `gorm:"not_null"`
Username string `protobuf:"bytes,9,opt,name=username,proto3" json:"username,omitempty" gorm:"not_null"`
// key_type specifies the key type to use when generating an SSH private key.
// Values must be "rsa", "ed25519", or "ecdsa".
// @inject_tag: `gorm:"not_null"`
KeyType string `protobuf:"bytes,10,opt,name=key_type,json=keyType,proto3" json:"key_type,omitempty" gorm:"not_null"`
// key_bits specifies the number of bits to use to generate an SSH private key.
// Not used if key_type is ed25519.
// @inject_tag: `gorm:"not_null"`
KeyBits uint32 `protobuf:"varint,11,opt,name=key_bits,json=keyBits,proto3" json:"key_bits,omitempty" gorm:"not_null"`
// ttl specifies the requested time to live for the certificate.
// @inject_tag: `gorm:"default:null"`
Ttl string `protobuf:"bytes,12,opt,name=ttl,proto3" json:"ttl,omitempty" gorm:"default:null"`
// key_id specifies the key id that the created certificate should have.
// @inject_tag: `gorm:"default:null"`
KeyId string `protobuf:"bytes,13,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"default:null"`
// critical_options specifies a map of the critical options that the certificate should be signed for.
// @inject_tag: `gorm:"default:null"`
CriticalOptions string `protobuf:"bytes,14,opt,name=critical_options,json=criticalOptions,proto3" json:"critical_options,omitempty" gorm:"default:null"`
// extensions specifies a map of the extensions that the certificate should be signed for.
// @inject_tag: `gorm:"default:null"`
Extensions string `protobuf:"bytes,15,opt,name=extensions,proto3" json:"extensions,omitempty" gorm:"default:null"`
// credential_type is always ssh_certificate
// @inject_tag: `gorm:"default:null"`
CredentialType string `protobuf:"bytes,16,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty" gorm:"default:null"`
}
func (x *SSHCertificateCredentialLibrary) Reset() {
*x = SSHCertificateCredentialLibrary{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SSHCertificateCredentialLibrary) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SSHCertificateCredentialLibrary) ProtoMessage() {}
func (x *SSHCertificateCredentialLibrary) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_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 SSHCertificateCredentialLibrary.ProtoReflect.Descriptor instead.
func (*SSHCertificateCredentialLibrary) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{4}
}
func (x *SSHCertificateCredentialLibrary) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *SSHCertificateCredentialLibrary) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *SSHCertificateCredentialLibrary) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *SSHCertificateCredentialLibrary) GetVaultPath() string {
if x != nil {
return x.VaultPath
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetKeyType() string {
if x != nil {
return x.KeyType
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetKeyBits() uint32 {
if x != nil {
return x.KeyBits
}
return 0
}
func (x *SSHCertificateCredentialLibrary) GetTtl() string {
if x != nil {
return x.Ttl
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetKeyId() string {
if x != nil {
return x.KeyId
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetCriticalOptions() string {
if x != nil {
return x.CriticalOptions
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetExtensions() string {
if x != nil {
return x.Extensions
}
return ""
}
func (x *SSHCertificateCredentialLibrary) GetCredentialType() string {
if x != nil {
return x.CredentialType
}
return ""
}
type Credential struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -688,7 +892,7 @@ type Credential struct {
func (x *Credential) Reset() {
*x = Credential{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -701,7 +905,7 @@ func (x *Credential) String() string {
func (*Credential) ProtoMessage() {}
func (x *Credential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -714,7 +918,7 @@ func (x *Credential) ProtoReflect() protoreflect.Message {
// Deprecated: Use Credential.ProtoReflect.Descriptor instead.
func (*Credential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{4}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{5}
}
func (x *Credential) GetPublicId() string {
@@ -832,7 +1036,7 @@ type UsernamePasswordOverride struct {
func (x *UsernamePasswordOverride) Reset() {
*x = UsernamePasswordOverride{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -845,7 +1049,7 @@ func (x *UsernamePasswordOverride) String() string {
func (*UsernamePasswordOverride) ProtoMessage() {}
func (x *UsernamePasswordOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -858,7 +1062,7 @@ func (x *UsernamePasswordOverride) ProtoReflect() protoreflect.Message {
// Deprecated: Use UsernamePasswordOverride.ProtoReflect.Descriptor instead.
func (*UsernamePasswordOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{5}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{6}
}
func (x *UsernamePasswordOverride) GetLibraryId() string {
@@ -923,7 +1127,7 @@ type SshPrivateKeyOverride struct {
func (x *SshPrivateKeyOverride) Reset() {
*x = SshPrivateKeyOverride{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[6]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -936,7 +1140,7 @@ func (x *SshPrivateKeyOverride) String() string {
func (*SshPrivateKeyOverride) ProtoMessage() {}
func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[6]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -949,7 +1153,7 @@ func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyOverride.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{6}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{7}
}
func (x *SshPrivateKeyOverride) GetLibraryId() string {
@@ -1146,72 +1350,131 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = []by
0x0f, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79,
0x12, 0x27, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74,
0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc3, 0x04, 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, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61,
0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d,
0x61, 0x63, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0xaa, 0x07, 0x0a, 0x1f, 0x53, 0x53,
0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x4b, 0x0a, 0x0b, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76,
0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65,
0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42,
0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x3f, 0x0a, 0x0a, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18,
0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x20, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x09, 0x56, 0x61, 0x75,
0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x52, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61,
0x74, 0x68, 0x12, 0x3f, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x09, 0x42, 0x23, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x07, 0x4b, 0x65, 0x79,
0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x54, 0x79,
0x70, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x0d, 0x42, 0x22, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x42,
0x69, 0x74, 0x73, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x42, 0x69, 0x74,
0x73, 0x12, 0x2b, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x42, 0x19,
0xc2, 0xdd, 0x29, 0x15, 0x0a, 0x03, 0x54, 0x74, 0x6c, 0x12, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x74, 0x6c, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x12, 0x35,
0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e,
0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x05, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x11, 0x61, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x52, 0x05,
0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x5d, 0x0a, 0x10, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61,
0x6c, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x42,
0x32, 0xc2, 0xdd, 0x29, 0x2e, 0x0a, 0x0f, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x2e, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x52, 0x0f, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x4f, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x42, 0x27, 0xc2, 0xdd, 0x29, 0x23, 0x0a, 0x0a,
0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x15, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a,
0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc3, 0x04, 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, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d, 0x61, 0x63, 0x12,
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f,
0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f,
0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x53, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77,
0x61, 0x62, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x65,
0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
0x97, 0x01, 0x0a, 0x18, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x75,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x15, 0x53, 0x73,
0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x4f, 0x76, 0x65, 0x72, 0x72,
0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79,
0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11,
0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x13, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x1d, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x70,
0x68, 0x72, 0x61, 0x73, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x42, 0x45,
0x5a, 0x43, 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, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75,
0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x6e,
0x65, 0x77, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76,
0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6c, 0x61, 0x73,
0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x53, 0x0a, 0x0f,
0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c,
0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x65, 0x6e, 0x65, 0x77,
0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x97, 0x01, 0x0a,
0x18, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62,
0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c,
0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72,
0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x41, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x15, 0x53, 0x73, 0x68, 0x50, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65,
0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12,
0x2d, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65,
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x32,
0x0a, 0x15, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x61, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61,
0x73, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x42, 0x45, 0x5a, 0x43, 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, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f,
0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1226,36 +1489,39 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP()
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData
}
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []interface{}{
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
(*ClientCertificate)(nil), // 2: controller.storage.credential.vault.store.v1.ClientCertificate
(*CredentialLibrary)(nil), // 3: controller.storage.credential.vault.store.v1.CredentialLibrary
(*Credential)(nil), // 4: controller.storage.credential.vault.store.v1.Credential
(*UsernamePasswordOverride)(nil), // 5: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*SshPrivateKeyOverride)(nil), // 6: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*timestamp.Timestamp)(nil), // 7: controller.storage.timestamp.v1.Timestamp
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
(*ClientCertificate)(nil), // 2: controller.storage.credential.vault.store.v1.ClientCertificate
(*CredentialLibrary)(nil), // 3: controller.storage.credential.vault.store.v1.CredentialLibrary
(*SSHCertificateCredentialLibrary)(nil), // 4: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary
(*Credential)(nil), // 5: controller.storage.credential.vault.store.v1.Credential
(*UsernamePasswordOverride)(nil), // 6: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*SshPrivateKeyOverride)(nil), // 7: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*timestamp.Timestamp)(nil), // 8: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
7, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 9: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 10: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 11: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 12: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
8, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
}
func init() { file_controller_storage_credential_vault_store_v1_vault_proto_init() }
@@ -1313,7 +1579,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
}
}
file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Credential); i {
switch v := v.(*SSHCertificateCredentialLibrary); i {
case 0:
return &v.state
case 1:
@@ -1325,7 +1591,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
}
}
file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UsernamePasswordOverride); i {
switch v := v.(*Credential); i {
case 0:
return &v.state
case 1:
@@ -1337,6 +1603,18 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
}
}
file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UsernamePasswordOverride); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SshPrivateKeyOverride); i {
case 0:
return &v.state
@@ -1355,7 +1633,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},
+6 -1
View File
@@ -75,9 +75,14 @@ func gotNewServer(t testing.TB, opt ...TestOption) *TestVaultServer {
pool: pool,
}
vaultVersion := DefaultVaultVersion
if opts.vaultVersion != "" {
vaultVersion = opts.vaultVersion
}
dockerOptions := &dockertest.RunOptions{
Repository: "vault",
Tag: DefaultVaultVersion,
Tag: vaultVersion,
Env: []string{fmt.Sprintf("VAULT_DEV_ROOT_TOKEN_ID=%s", server.RootToken)},
}
+154 -13
View File
@@ -140,6 +140,38 @@ func TestCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, stor
return libs
}
// TestSSHCertificateCredentialLibraries creates count number of vault ssh
// certificate credential libraries in the provided DB with the provided store
// id. If any errors are encountered during the creation of the credential
// libraries, the test will fail.
func TestSSHCertificateCredentialLibraries(t testing.TB, conn *db.DB, _ wrapping.Wrapper, storeId string, count int) []*SSHCertificateCredentialLibrary {
t.Helper()
assert, require := assert.New(t), require.New(t)
w := db.New(conn)
var libs []*SSHCertificateCredentialLibrary
for i := 0; i < count; i++ {
lib, err := NewSSHCertificateCredentialLibrary(storeId, fmt.Sprintf("ssh/sign/role-%d", i), "username", WithKeyType(KeyTypeEd25519))
assert.NoError(err)
require.NotNil(lib)
id, err := newSSHCertificateCredentialLibraryId()
assert.NoError(err)
require.NotEmpty(id)
lib.PublicId = id
ctx := context.Background()
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
return iw.Create(ctx, lib)
},
)
require.NoError(err2)
libs = append(libs, lib)
}
return libs
}
// TestCredentials creates count number of vault credentials in the provided DB with
// the provided library id and session id. If any errors are encountered
// during the creation of the credentials, the test will fail.
@@ -168,7 +200,7 @@ func TestCredentials(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, librar
var credentials []*Credential
for i := 0; i < count; i++ {
credential, err := newCredential(lib.GetPublicId(), sessionId, fmt.Sprintf("vault/credential/%d", i), token.GetTokenHmac(), 5*time.Minute)
credential, err := newCredential(lib.GetPublicId(), fmt.Sprintf("vault/credential/%d", i), token.GetTokenHmac(), 5*time.Minute)
assert.NoError(err)
require.NotNil(credential)
@@ -177,7 +209,7 @@ func TestCredentials(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, librar
require.NotNil(id)
credential.PublicId = id
query, queryValues := credential.insertQuery()
query, queryValues := insertQuery(credential, sessionId)
rows, err2 := rw.Exec(ctx, query, queryValues)
assert.Equal(1, rows)
assert.NoError(err2)
@@ -477,17 +509,19 @@ type TestOption func(testing.TB, *testOptions)
// options = how options are represented
type testOptions struct {
orphan bool
periodic bool
renewable bool
policies []string
mountPath string
roleName string
vaultTLS TestVaultTLS
dockerNetwork bool
skipCleanup bool
tokenPeriod time.Duration
clientKey *ecdsa.PrivateKey
orphan bool
periodic bool
renewable bool
policies []string
allowedExtensions []string
mountPath string
roleName string
vaultTLS TestVaultTLS
dockerNetwork bool
skipCleanup bool
tokenPeriod time.Duration
clientKey *ecdsa.PrivateKey
vaultVersion string
}
func getDefaultTestOptions(t testing.TB) testOptions {
@@ -593,6 +627,24 @@ func WithDontCleanUp() TestOption {
}
}
// WithVaultVersion sets the version of vault that will be started.
// defaults to the value stored in vault.supported.DefaultVaultVersion
func WithVaultVersion(s string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.vaultVersion = s
}
}
// WithAllowedExtension tells vault to allow a specific SSH extension
// to be used by vault's ssh secrets engine
func WithAllowedExtension(e string) TestOption {
return func(t testing.TB, o *testOptions) {
t.Helper()
o.allowedExtensions = append(o.allowedExtensions, e)
}
}
// TestOrphanToken sets the token orphan option to b.
// The orphan option is true by default.
func TestOrphanToken(b bool) TestOption {
@@ -752,6 +804,95 @@ func (v *TestVaultServer) addPolicy(t testing.TB, name string, pc pathCapabiliti
require.NoError(vc.Sys().PutPolicy(name, policy))
}
// MountSSH mounts the Vault SSH secret engine and initializes it by
// generating a root certificate authority and creating a default role on
// the mount. The root CA is returned.
//
// The default mount path is ssh and the default role name is boundary.
// WithTestMountPath, WithTestRoleName, and WithAllowedExtension are the
// test options supported. extensions are combined into allowed_extensions.
// allowed_extensions defaults to "*" (allow all)
// The role is defined as:
//
// {
// "key_type": "ca",
// "allowed_users": "*",
// "allowed_extensions": "*",
// "allow_user_certificates": true,
// "ttl": "12h0m0s"
// }
//
// MountSSH also adds a Vault policy named 'ssh' to v and adds it to the
// standard set of polices attached to tokens created with v.CreateToken.
// The policy is defined as:
//
// path "mountPath/*" {
// capabilities = ["create", "read", "update", "delete", "list"]
// }
func (v *TestVaultServer) MountSSH(t testing.TB, opt ...TestOption) *vault.Secret {
require := require.New(t)
opts := getTestOpts(t, opt...)
vc := v.client(t).cl
// Mount SSH
maxTTL := 24 * time.Hour
if t, ok := t.(*testing.T); ok {
if deadline, ok := t.Deadline(); ok {
maxTTL = time.Until(deadline) * 2
}
}
defaultTTL := maxTTL / 2
t.Logf("maxTTL: %s, defaultTTL: %s", maxTTL, defaultTTL)
mountInput := &vault.MountInput{
Type: "ssh",
Description: t.Name(),
Config: vault.MountConfigInput{
DefaultLeaseTTL: defaultTTL.String(),
MaxLeaseTTL: maxTTL.String(),
},
}
mountPath := opts.mountPath
if mountPath == "" {
mountPath = "ssh/"
}
// this is the call that actually enables the secrets engine
require.NoError(vc.Sys().Mount(mountPath, mountInput))
policyPath := fmt.Sprintf("%s*", mountPath)
pc := pathCapabilities{
policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability,
}
v.addPolicy(t, "ssh", pc)
// Generate a root CA
caPath := path.Join(mountPath, "config/ca")
caOptions := map[string]any{
"generate_signing_key": true,
}
s, err := vc.Logical().Write(caPath, caOptions)
require.NoError(err)
require.NotEmpty(s)
// Create default role
rolePath := path.Join(mountPath, "roles", opts.roleName)
allowExtensions := "*"
if len(opts.allowedExtensions) > 0 {
allowExtensions = strings.Join(opts.allowedExtensions, ",")
}
roleOptions := map[string]any{
"key_type": "ca",
"allowed_users": "*",
"allowed_extensions": allowExtensions,
"allow_user_certificates": true,
"ttl": defaultTTL.String(),
}
_, err = vc.Logical().Write(rolePath, roleOptions)
require.NoError(err)
return s
}
// MountPKI mounts the Vault PKI secret engine and initializes it by
// generating a root certificate authority and creating a default role on
// the mount. The root CA is returned.
@@ -2,6 +2,7 @@ package credentiallibraries
import (
"context"
"encoding/json"
"fmt"
"strings"
@@ -33,6 +34,11 @@ const (
httpMethodField = "attributes.http_method"
httpRequestBodyField = "attributes.http_request_body"
credentialMappingPathField = "credential_mapping_overrides"
sshCertUsernameField = "attributes.username"
keyTypeField = "attributes.key_type"
keyBitsField = "attributes.key_bits"
criticalOptionsField = "attributes.critical_options"
extensionsField = "attributes.extensions"
domain = "credential"
)
@@ -45,7 +51,8 @@ const (
)
var (
maskManager handlers.MaskManager
maskManager handlers.MaskManager
sshCertMaskManager handlers.MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
@@ -62,6 +69,18 @@ var (
action.Create,
action.List,
}
validCredentialTypesVaultGeneric = []credential.Type{
credential.UsernamePasswordType,
credential.SshPrivateKeyType,
credential.UnspecifiedType,
}
validKeyTypes = []string{
vault.KeyTypeEcdsa,
vault.KeyTypeEd25519,
vault.KeyTypeRsa,
}
)
func init() {
@@ -70,6 +89,10 @@ func init() {
handlers.MaskSource{&pb.CredentialLibrary{}, &pb.VaultCredentialLibraryAttributes{}}); err != nil {
panic(err)
}
if sshCertMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.SSHCertificateCredentialLibrary{}},
handlers.MaskSource{&pb.CredentialLibrary{}, &pb.VaultSSHCertificateCredentialLibraryAttributes{}}); err != nil {
panic(err)
}
}
// Service handles request as described by the pbs.CredentialLibraryServiceServer interface.
@@ -242,11 +265,23 @@ func (s Service) UpdateCredentialLibrary(ctx context.Context, req *pbs.UpdateCre
if err != nil {
return nil, err
}
cur, err := repo.LookupCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
var currentCredentialType credential.Type
var mo vault.MappingOverride
switch subtypes.SubtypeFromId(domain, req.GetId()) {
case vault.SSHCertificateLibrarySubtype:
cur, err := repo.LookupSSHCertificateCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType = credential.Type(cur.GetCredentialType())
default:
cur, err := repo.LookupCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType = credential.Type(cur.GetCredentialType())
mo = cur.MappingOverride
}
currentCredentialType := credential.Type(cur.GetCredentialType())
if err := validateUpdateRequest(req, currentCredentialType); err != nil {
return nil, err
@@ -255,7 +290,7 @@ func (s Service) UpdateCredentialLibrary(ctx context.Context, req *pbs.UpdateCre
if authResults.Error != nil {
return nil, authResults.Error
}
cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem(), currentCredentialType, cur.MappingOverride)
cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem(), currentCredentialType, mo)
if err != nil {
return nil, err
}
@@ -298,16 +333,28 @@ func (s Service) DeleteCredentialLibrary(ctx context.Context, req *pbs.DeleteCre
return nil, nil
}
func (s Service) listFromRepo(ctx context.Context, storeId string) ([]*vault.CredentialLibrary, error) {
func (s Service) listFromRepo(ctx context.Context, storeId string) ([]credential.Library, error) {
const op = "credentiallibraries.(Service).listFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
csl, err := repo.ListCredentialLibraries(ctx, storeId)
genCsl, err := repo.ListCredentialLibraries(ctx, storeId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
certCsl, err := repo.ListSSHCertificateCredentialLibraries(ctx, storeId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
csl := make([]credential.Library, 0, len(genCsl)+len(certCsl))
for _, s := range genCsl {
csl = append(csl, s)
}
for _, s := range certCsl {
csl = append(csl, s)
}
return csl, nil
}
@@ -317,32 +364,67 @@ func (s Service) getFromRepo(ctx context.Context, id string) (credential.Library
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs, err := repo.LookupCredentialLibrary(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
switch subtypes.SubtypeFromId(domain, id) {
case vault.GenericLibrarySubtype:
cs, err := repo.LookupCredentialLibrary(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential library %q not found", id))
}
return cs, err
case vault.SSHCertificateLibrarySubtype:
cs, err := repo.LookupSSHCertificateCredentialLibrary(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("ssh certificate credential library %q not found", id))
}
return cs, err
}
if cs == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential library %q not found", id))
}
return cs, err
return nil, errors.New(ctx, errors.InvalidParameter, op, "unrecognized credential library subtype")
}
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.CredentialLibrary) (credential.Library, error) {
const op = "credentiallibraries.(Service).createInRepo"
cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateCredentialLibrary(ctx, scopeId, cl)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential library"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential library but no error returned from repository.")
var out credential.Library
switch subtypes.SubtypeFromType(domain, item.GetType()) {
case vault.SSHCertificateLibrarySubtype:
cl, err := toStorageVaultSSHCertificateLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rl, err := repo.CreateSSHCertificateCredentialLibrary(ctx, scopeId, cl)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create ssh certificate credential library"))
}
if rl == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create ssh certificate credential library but no error returned from repository.")
}
out = rl
default:
cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rl, err := repo.CreateCredentialLibrary(ctx, scopeId, cl)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential library"))
}
if rl == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential library but no error returned from repository.")
}
out = rl
}
return out, nil
}
@@ -372,26 +454,53 @@ func (s Service) updateInRepo(
item.CredentialMappingOverrides = mappingStruct
}
cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
dbMasks = append(dbMasks, maskManager.Translate(masks)...)
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
var out credential.Library
rowsUpdated := 0
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
switch subtypes.SubtypeFromId(domain, id) {
case vault.SSHCertificateLibrarySubtype:
dbMasks = append(dbMasks, sshCertMaskManager.Translate(masks)...)
if getMapUpdate(criticalOptionsField, masks) {
dbMasks = append(dbMasks, vault.CriticalOptionsField)
}
if getMapUpdate(extensionsField, masks) {
dbMasks = append(dbMasks, vault.ExtensionsField)
}
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
cl, err := toStorageVaultSSHCertificateLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
out, rowsUpdated, err = repo.UpdateSSHCertificateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
}
default:
dbMasks = append(dbMasks, maskManager.Translate(masks)...)
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
out, rowsUpdated, err = repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id)
}
}
return out, nil
}
@@ -402,7 +511,13 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
if err != nil {
return false, err
}
rows, err := repo.DeleteCredentialLibrary(ctx, scopeId, id)
rows := 0
switch subtypes.SubtypeFromId(domain, id) {
case vault.SSHCertificateLibrarySubtype:
rows, err = repo.DeleteSSHCertificateCredentialLibrary(ctx, scopeId, id)
default:
rows, err = repo.DeleteCredentialLibrary(ctx, scopeId, id)
}
if err != nil {
if errors.IsNotFoundError(err) {
return false, nil
@@ -428,9 +543,9 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
parentId = id
default:
opts = append(opts, auth.WithId(id))
parentId = id
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
case vault.GenericLibrarySubtype:
cl, err := repo.LookupCredentialLibrary(ctx, id)
if err != nil {
res.Error = err
@@ -441,6 +556,17 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return res
}
parentId = cl.GetStoreId()
case vault.SSHCertificateLibrarySubtype:
cl, err := repo.LookupSSHCertificateCredentialLibrary(ctx, id)
if err != nil {
res.Error = err
return res
}
if cl == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cl.GetStoreId()
default:
res.Error = errors.New(ctx, errors.InvalidParameter, op, "unrecognized credential library subtype from id")
return res
@@ -515,7 +641,7 @@ func toProto(in credential.Library, opt ...handlers.Option) (*pb.CredentialLibra
out.AuthorizedActions = opts.WithAuthorizedActions
}
switch subtypes.SubtypeFromId(domain, in.GetPublicId()) {
case vault.Subtype:
case vault.GenericLibrarySubtype:
vaultIn, ok := in.(*vault.CredentialLibrary)
if !ok {
return nil, errors.NewDeprecated(errors.Internal, op, "unable to cast to vault credential library")
@@ -568,6 +694,47 @@ func toProto(in credential.Library, opt ...handlers.Option) (*pb.CredentialLibra
VaultCredentialLibraryAttributes: attrs,
}
}
case vault.SSHCertificateLibrarySubtype:
vaultIn, ok := in.(*vault.SSHCertificateCredentialLibrary)
if !ok {
return nil, errors.NewDeprecated(errors.Internal, op, "unable to cast to vault ssh certificate credential library")
}
// We don't check for mapping overrides here -- this subtype does not currently support them.
out.CredentialType = vaultIn.GetCredentialType()
if outputFields.Has(globals.AttributesField) {
attrs := &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String(vaultIn.GetVaultPath()),
}
if vaultIn.GetUsername() != "" {
attrs.Username = wrapperspb.String(vaultIn.GetUsername())
}
if vaultIn.GetKeyType() != "" {
attrs.KeyType = wrapperspb.String(vaultIn.GetKeyType())
}
if vaultIn.GetKeyBits() != 0 {
attrs.KeyBits = &wrapperspb.UInt32Value{Value: vaultIn.GetKeyBits()}
}
if vaultIn.GetTtl() != "" {
attrs.Ttl = wrapperspb.String(vaultIn.GetTtl())
}
if vaultIn.GetKeyId() != "" {
attrs.KeyId = wrapperspb.String(vaultIn.GetKeyId())
}
if vaultIn.GetCriticalOptions() != "" {
co := make(map[string]string)
json.Unmarshal([]byte(vaultIn.GetCriticalOptions()), &co)
attrs.CriticalOptions = co
}
if vaultIn.GetExtensions() != "" {
e := make(map[string]string)
json.Unmarshal([]byte(vaultIn.GetExtensions()), &e)
attrs.Extensions = e
}
out.Attrs = &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: attrs,
}
}
}
return &out, nil
}
@@ -582,7 +749,11 @@ func toStorageVaultLibrary(storeId string, in *pb.CredentialLibrary) (out *vault
opts = append(opts, vault.WithDescription(in.GetDescription().GetValue()))
}
attrs := in.GetVaultCredentialLibraryAttributes()
attrs := in.GetVaultGenericCredentialLibraryAttributes()
if attrs == nil {
// fallback to attributes for older subtype
attrs = in.GetVaultCredentialLibraryAttributes()
}
if attrs.GetHttpMethod() != nil {
opts = append(opts, vault.WithMethod(vault.Method(strings.ToUpper(attrs.GetHttpMethod().GetValue()))))
}
@@ -631,13 +802,66 @@ func toStorageVaultLibrary(storeId string, in *pb.CredentialLibrary) (out *vault
return cs, err
}
func toStorageVaultSSHCertificateLibrary(storeId string, in *pb.CredentialLibrary) (out *vault.SSHCertificateCredentialLibrary, err error) {
const op = "credentiallibraries.toStorageVaultSSHCertificateLibrary"
var opts []vault.Option
if in.GetName() != nil {
opts = append(opts, vault.WithName(in.GetName().GetValue()))
}
if in.GetDescription() != nil {
opts = append(opts, vault.WithDescription(in.GetDescription().GetValue()))
}
opts = append(opts, vault.WithCredentialType(credential.Type(in.GetCredentialType())))
attrs := in.GetVaultSshCertificateCredentialLibraryAttributes()
if attrs.GetKeyType() != nil {
opts = append(opts, vault.WithKeyType(attrs.GetKeyType().GetValue()))
}
if attrs.GetKeyBits() != nil {
opts = append(opts, vault.WithKeyBits(attrs.GetKeyBits().GetValue()))
}
if attrs.GetTtl() != nil {
opts = append(opts, vault.WithTtl(attrs.GetTtl().GetValue()))
}
if attrs.GetKeyId() != nil {
opts = append(opts, vault.WithKeyId(attrs.GetKeyId().GetValue()))
}
if attrs.GetCriticalOptions() != nil {
co, err := json.Marshal(attrs.GetCriticalOptions())
if err != nil {
return nil, err
}
opts = append(opts, vault.WithCriticalOptions(string(co)))
}
if attrs.GetExtensions() != nil {
e, err := json.Marshal(attrs.GetExtensions())
if err != nil {
return nil, err
}
opts = append(opts, vault.WithExtensions(string(e)))
}
cs, err := vault.NewSSHCertificateCredentialLibrary(storeId, attrs.GetPath().GetValue(), attrs.GetUsername().GetValue(), opts...)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential library"))
}
return cs, err
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// - The path passed in is correctly formatted
// - All required parameters are set
// - There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetCredentialLibraryRequest) error {
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialLibraryPrefix)
prefix := ""
switch subtypes.SubtypeFromId(domain, req.GetId()) {
case vault.SSHCertificateLibrarySubtype:
prefix = vault.SSHCertificateCredentialLibraryPrefix
default:
prefix = vault.CredentialLibraryPrefix
}
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, prefix)
}
func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
@@ -645,23 +869,82 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
badFields := map[string]string{}
switch subtypes.SubtypeFromId(domain, req.GetItem().GetCredentialStoreId()) {
case vault.Subtype:
if t := req.GetItem().GetType(); t != "" && subtypes.SubtypeFromType(domain, t) != vault.Subtype {
t := req.GetItem().GetType()
if t == "" {
badFields[globals.TypeField] = "This is a required field."
}
// support older vault generic libraries with 'vault.Subtype'.
if subtypes.SubtypeFromType(domain, t) == vault.Subtype {
req.GetItem().Type = vault.GenericLibrarySubtype.String()
t = vault.GenericLibrarySubtype.String()
}
if subtypes.SubtypeFromType(domain, t) != vault.GenericLibrarySubtype &&
subtypes.SubtypeFromType(domain, t) != vault.SSHCertificateLibrarySubtype {
badFields[globals.CredentialStoreIdField] = "If included, type must match that of the credential store."
}
attrs := req.GetItem().GetVaultCredentialLibraryAttributes()
if attrs == nil {
badFields[attributesPathField] = "This is a required field."
switch subtypes.SubtypeFromType(domain, req.GetItem().GetType()) {
case vault.GenericLibrarySubtype:
isValidCred := false
ct := req.GetItem().GetCredentialType()
for _, t := range validCredentialTypesVaultGeneric {
if ct == "" || ct == string(t) {
isValidCred = true
break
}
}
if !isValidCred {
badFields[globals.CredentialTypeField] = fmt.Sprintf("Unknown credential type %q", ct)
}
attrs := req.GetItem().GetVaultGenericCredentialLibraryAttributes()
if attrs == nil {
// fallback to attributes for older subtype
attrs = req.GetItem().GetVaultCredentialLibraryAttributes()
if attrs == nil {
badFields[attributesPathField] = "This is a required field."
}
}
if attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field."
}
if m := attrs.GetHttpMethod(); m != nil && !strutil.StrListContains([]string{"GET", "POST"}, strings.ToUpper(m.GetValue())) {
badFields[httpMethodField] = "If set, value must be 'GET' or 'POST'."
}
if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) != "POST" {
badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField)
}
validateMapping(badFields, credential.Type(req.GetItem().GetCredentialType()), req.GetItem().CredentialMappingOverrides.AsMap())
case vault.SSHCertificateLibrarySubtype:
if req.GetItem().GetCredentialType() != "" {
badFields[globals.CredentialTypeField] = fmt.Sprintf("This field is read only and cannot be set.")
}
attrs := req.GetItem().GetVaultSshCertificateCredentialLibraryAttributes()
if attrs == nil {
badFields[attributesPathField] = "This is a required field."
}
if attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field."
}
if attrs.GetUsername().GetValue() == "" {
badFields[sshCertUsernameField] = "This is a required field."
}
if (attrs.GetKeyType() == nil) != (attrs.GetKeyBits() == nil) {
if attrs.GetKeyType() != nil && attrs.GetKeyType().GetValue() != vault.KeyTypeEd25519 {
badFields[keyTypeField] = fmt.Sprintf("If set, %q must also be set.", keyBitsField)
}
if attrs.GetKeyBits() != nil {
badFields[keyBitsField] = fmt.Sprintf("If set, %q must also be set.", keyTypeField)
}
}
if t := attrs.GetKeyType(); t != nil && !strutil.StrListContains(validKeyTypes, strings.ToLower(t.GetValue())) {
badFields[keyTypeField] = "If set, value must be 'ed25519', 'ecdsa', or 'rsa'."
}
validateKeyBits(badFields, attrs.GetKeyBits().GetValue(), attrs.GetKeyType().GetValue())
}
if attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field."
}
if m := attrs.GetHttpMethod(); m != nil && !strutil.StrListContains([]string{"GET", "POST"}, strings.ToUpper(m.GetValue())) {
badFields[httpMethodField] = "If set, value must be 'GET' or 'POST'."
}
if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) != "POST" {
badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField)
}
validateMapping(badFields, credential.Type(req.GetItem().GetCredentialType()), req.GetItem().CredentialMappingOverrides.AsMap())
default:
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
}
@@ -670,17 +953,29 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
}
func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest, currentCredentialType credential.Type) error {
prefix := ""
st := subtypes.SubtypeFromId(domain, req.GetId())
switch st {
case vault.GenericLibrarySubtype:
prefix = vault.CredentialLibraryPrefix
case vault.SSHCertificateLibrarySubtype:
prefix = vault.SSHCertificateCredentialLibraryPrefix
}
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
switch subtypes.SubtypeFromId(domain, req.GetId()) {
case vault.Subtype:
if req.GetItem().GetType() != "" && subtypes.SubtypeFromType(domain, req.GetItem().GetType()) != vault.Subtype {
switch st {
case vault.GenericLibrarySubtype:
if req.GetItem().GetType() != "" && subtypes.SubtypeFromType(domain, req.GetItem().GetType()) != vault.GenericLibrarySubtype {
badFields[globals.TypeField] = "Cannot modify resource type."
}
if req.GetItem().GetCredentialType() != "" && req.GetItem().GetCredentialType() != string(currentCredentialType) {
badFields[globals.CredentialTypeField] = "Cannot modify credential type."
}
attrs := req.GetItem().GetVaultCredentialLibraryAttributes()
attrs := req.GetItem().GetVaultGenericCredentialLibraryAttributes()
if attrs == nil {
// fallback to attributes for older subtype
attrs = req.GetItem().GetVaultCredentialLibraryAttributes()
}
if attrs != nil {
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), vaultPathField) && attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field and cannot be set to empty."
@@ -693,13 +988,33 @@ func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest, currentCrede
}
validateMapping(badFields, currentCredentialType, req.GetItem().CredentialMappingOverrides.AsMap())
}
case vault.SSHCertificateLibrarySubtype:
if req.GetItem().GetType() != "" && subtypes.SubtypeFromType(domain, req.GetItem().GetType()) != vault.SSHCertificateLibrarySubtype {
badFields[globals.TypeField] = "Cannot modify resource type."
}
if req.GetItem().GetCredentialType() != "" && req.GetItem().GetCredentialType() != string(currentCredentialType) {
badFields[globals.CredentialTypeField] = "Cannot modify credential type."
}
attrs := req.GetItem().GetVaultSshCertificateCredentialLibraryAttributes()
if attrs != nil {
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), vaultPathField) && attrs.GetPath().GetValue() == "" {
badFields[vaultPathField] = "This is a required field and cannot be set to empty."
}
if u := attrs.GetUsername().GetValue(); handlers.MaskContains(req.GetUpdateMask().GetPaths(), sshCertUsernameField) && u == "" {
badFields[sshCertUsernameField] = "This is a required field and cannot be set to empty."
}
if t := attrs.GetKeyType(); t != nil && !strutil.StrListContains(validKeyTypes, strings.ToLower(t.GetValue())) {
badFields[keyTypeField] = "If set, value must be 'ed25519', 'ecdsa', or 'rsa'."
}
validateKeyBits(badFields, attrs.GetKeyBits().GetValue(), attrs.GetKeyType().GetValue())
}
}
return badFields
}, vault.CredentialLibraryPrefix)
}, prefix)
}
func validateDeleteRequest(req *pbs.DeleteCredentialLibraryRequest) error {
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialLibraryPrefix)
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialLibraryPrefix, vault.SSHCertificateCredentialLibraryPrefix)
}
func validateListRequest(req *pbs.ListCredentialLibrariesRequest) error {
@@ -747,6 +1062,38 @@ func validateMapping(badFields map[string]string, credentialType credential.Type
}
}
// validateKeyBits appends to badFields if keyBits and keyType aren't accepted combinations for an SSHCertificateCredentialLibrary.
// If keyType is an empty string, validateKeyBits only validates keyBits.
func validateKeyBits(badFields map[string]string, keyBits uint32, keyType string) {
switch keyBits {
case vault.KeyBitsDefault:
case vault.KeyBitsEcdsa256, vault.KeyBitsEcdsa384, vault.KeyBitsEcdsa521:
if keyType != "" && keyType != vault.KeyTypeEcdsa {
badFields[keyBitsField] = fmt.Sprintf("Invalid bit size %d for key type %s", keyBits, keyType)
}
case vault.KeyBitsRsa2048, vault.KeyBitsRsa3072, vault.KeyBitsRsa4096:
if keyType != "" && keyType != vault.KeyTypeRsa {
badFields[keyBitsField] = fmt.Sprintf("Invalid bit size %d for key type %s", keyBits, keyType)
}
default:
badFields[keyBitsField] = fmt.Sprintf("Invalid bit size %d", keyBits)
}
}
func getMapUpdate(field string, apiMasks []string) bool {
for _, m := range apiMasks {
if m == field {
return true
}
fieldPrefix := fmt.Sprintf("%v.", field)
if s := strings.SplitN(m, fieldPrefix, 2); len(s) == 2 {
return true
}
}
return false
}
func getMappingUpdates(credentialType credential.Type, current vault.MappingOverride, new map[string]any, apiMasks []string) (map[string]any, bool) {
ret := make(map[string]any)
masks := make(map[string]bool)
@@ -33,7 +33,16 @@ func dynamicToWorkerCredential(ctx context.Context, cred credential.Dynamic) (se
},
},
}
case credential.SshCertificate:
workerCred = &serverpb.Credential{
Credential: &serverpb.Credential_SshCertificate{
SshCertificate: &serverpb.SshCertificate{
Username: c.Username(),
PrivateKey: string(c.PrivateKey()),
Certificate: string(c.Certificate()),
},
},
}
case credential.SshPrivateKey:
workerCred = &serverpb.Credential{
Credential: &serverpb.Credential_SshPrivateKey{
@@ -1810,6 +1810,7 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
for _, cl := range req.GetInjectedApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
vault.CredentialLibraryPrefix,
vault.SSHCertificateCredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
@@ -1857,6 +1858,7 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
for _, cl := range req.GetInjectedApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
vault.CredentialLibraryPrefix,
vault.SSHCertificateCredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
@@ -1908,6 +1910,7 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
for _, cl := range req.GetInjectedApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl),
vault.CredentialLibraryPrefix,
vault.SSHCertificateCredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix,
@@ -2516,6 +2516,7 @@ func TestAuthorizeSession(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Library Name"),
Description: wrapperspb.String("Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: &wrapperspb.StringValue{
@@ -2629,7 +2630,7 @@ func TestAuthorizeSession(t *testing.T) {
Name: clsResp.GetItem().GetName().GetValue(),
Description: clsResp.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
},
}},
// TODO: validate the contents of the authorization token is what is expected
@@ -2758,6 +2759,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Usernamepassword Library"),
Description: wrapperspb.String("Usernamepassword Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")),
@@ -2772,6 +2774,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Unspecified Library"),
Description: wrapperspb.String("Unspecified Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")),
@@ -2789,6 +2792,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Usernamepassword Mapping Library"),
Description: wrapperspb.String("Usernamepassword Mapping Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "non-default-userpass")),
@@ -2860,6 +2864,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("SSH Private Key Library"),
Description: wrapperspb.String("SSH Private Key Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "default-sshpk")),
@@ -2878,6 +2883,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("SSH Private Key Mapping Library"),
Description: wrapperspb.String("SSH Private Key Mapping Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "non-default-sshpk")),
@@ -2900,6 +2906,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("SSH Private Key With Passphrase Library"),
Description: wrapperspb.String("SSH Private Key Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "default-sshpk-with-pass")),
@@ -2919,6 +2926,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("SSH Private Key With Passphrase Mapping Library"),
Description: wrapperspb.String("SSH Private Key Mapping Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "non-default-sshpk-with-pass")),
@@ -2955,7 +2963,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespUnspecified.GetItem().GetName().GetValue(),
Description: clsRespUnspecified.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
},
},
},
@@ -2971,7 +2979,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespUsernamePassword.GetItem().GetName().GetValue(),
Description: clsRespUsernamePassword.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.UsernamePasswordType),
},
Credential: func() *structpb.Struct {
@@ -2997,7 +3005,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespUsernamePasswordWithMapping.GetItem().GetName().GetValue(),
Description: clsRespUsernamePasswordWithMapping.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.UsernamePasswordType),
},
Credential: func() *structpb.Struct {
@@ -3075,7 +3083,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespSshPrivateKey.GetItem().GetName().GetValue(),
Description: clsRespSshPrivateKey.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.SshPrivateKeyType),
},
Credential: func() *structpb.Struct {
@@ -3101,7 +3109,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespSshPrivateKeyWithMapping.GetItem().GetName().GetValue(),
Description: clsRespSshPrivateKeyWithMapping.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.SshPrivateKeyType),
},
Credential: func() *structpb.Struct {
@@ -3154,7 +3162,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespSshPrivateKeyWithPass.GetItem().GetName().GetValue(),
Description: clsRespSshPrivateKeyWithPass.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.SshPrivateKeyType),
},
Credential: func() *structpb.Struct {
@@ -3181,7 +3189,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
Name: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetName().GetValue(),
Description: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.Subtype.String(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(credential.SshPrivateKeyType),
},
Credential: func() *structpb.Struct {
@@ -3392,6 +3400,7 @@ func TestAuthorizeSession_Errors(t *testing.T) {
clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("database", "creds", "opened")),
@@ -3416,6 +3425,7 @@ func TestAuthorizeSession_Errors(t *testing.T) {
clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("bad path"),
@@ -3440,6 +3450,7 @@ func TestAuthorizeSession_Errors(t *testing.T) {
clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: expiredStore.GetPublicId(),
Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("database", "creds", "opened")),
@@ -65,6 +65,7 @@ begin;
and o.public_id = p.parent_id
and o.type = 'org';
-- Replaced in 63/03_wh_ssh_cert_library.up.sql
create view whx_credential_dimension_target as
select key,
credential_purpose,
@@ -4,6 +4,7 @@ begin;
-- with the current values in the operational tables for the given parameters. IF the values
-- between operational tables and the wh_credential_dimension differ, a new row is inserted in
-- the wh_credential_dimension to match the current values in the operational tables.
-- Replaced in 63/03_wh_ssh_cert_library.up.sql
create function wh_upsert_credential_dimension(p_session_id wt_public_id, p_library_id wt_public_id, p_credential_purpose wh_dim_text) returns wh_dim_key
as $$
declare
@@ -6,6 +6,7 @@ begin;
-- Add new constraint that only allows known types
-- This replaces the constraint defined in 36/01_credential_type_up
-- Replaced in 63/01_credential_vault_ssh_cert_library
alter table credential_type_enm
add constraint only_predefined_credential_types_allowed
check (
@@ -157,6 +157,7 @@ begin;
-- The whx_credential_dimension_source view shows the current values in the
-- operational tables of the credential dimension.
-- Replaces whx_credential_dimension_source defined in 16/02_wh_credential_dimension.up.sql
-- Replaced in 63/03_wh_ssh_cert_library.up.sql
drop view whx_credential_dimension_source;
create view whx_credential_dimension_source as
select -- id is the first column in the target view
@@ -188,6 +188,7 @@ begin;
'The view returns the current token for the store, if the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaced in 63/02_add_ssh_cert_to_vault_cred_library_view.up.sql
create view credential_vault_library_issue_credentials as
with
password_override (library_id, username_attribute, password_attribute) as (
@@ -0,0 +1,170 @@
begin;
-- drop constraint so we can add ssh_certificate
alter table credential_type_enm
drop constraint only_predefined_credential_types_allowed;
-- Add new constraint that only allows known types
-- This replaces the constraint defined in 39/01_static_ssh_private_key_creds_up
alter table credential_type_enm
add constraint only_predefined_credential_types_allowed
check (
name in (
'unspecified',
'username_password',
'ssh_private_key',
'ssh_certificate'
)
);
insert into credential_type_enm (name)
values ('ssh_certificate');
create table credential_vault_ssh_cert_key_type_enm (
name text primary key
constraint only_predefined_key_types_allowed
check (
name in (
'ed25519',
'ecdsa',
'rsa'
)
)
);
comment on table credential_vault_ssh_cert_key_type_enm is
'credential_vault_ssh_cert_key_type_enm is an enumeration table for the ssh key type. ';
insert into credential_vault_ssh_cert_key_type_enm (name)
values
('ed25519'),
('ecdsa'),
('rsa');
create table credential_vault_ssh_cert_key_bits_enm (
bits int primary key
constraint only_predefined_key_bits_allowed
check (
bits in (
0,
2048,
3072,
4096,
256,
384,
521
)
)
);
comment on table credential_vault_ssh_cert_key_bits_enm is
'credential_vault_ssh_cert_key_bits_enm is an enumeration table for the ssh key bits. ';
insert into credential_vault_ssh_cert_key_bits_enm (bits)
values
(0),
(2048),
(3072),
(4096),
(256),
(384),
(521);
create table credential_vault_ssh_cert_valid_key_type_key_bits (
key_type text not null
constraint credential_vault_ssh_cert_key_type_enm_fkey
references credential_vault_ssh_cert_key_type_enm (name),
key_bits int not null
constraint credential_vault_ssh_cert_key_bits_enm_fkey
references credential_vault_ssh_cert_key_bits_enm (bits),
constraint credential_vault_ssh_cert_valid_key_type_key_bits_uq
unique(key_type, key_bits)
);
insert into credential_vault_ssh_cert_valid_key_type_key_bits (key_type, key_bits)
values
('ed25519', 0),
('ecdsa', 256),
('ecdsa', 384),
('ecdsa', 521),
('rsa', 2048),
('rsa', 3072),
('rsa', 4096);
create table credential_vault_ssh_cert_library (
public_id wt_public_id primary key,
store_id wt_public_id not null
constraint credential_vault_store_fkey
references credential_vault_store (public_id)
on delete cascade
on update cascade,
name wt_name,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
vault_path text not null
constraint vault_path_must_not_be_empty
check(length(trim(vault_path)) > 0)
constraint vault_path_must_be_sign_or_issue
check(vault_path ~ '^.+\/(sign|issue)\/[^\/\\\s]+$'),
username text not null
constraint username_must_not_be_empty
check(length(trim(username)) > 0),
key_type text not null,
key_bits int not null,
ttl text,
key_id text,
critical_options bytea,
extensions bytea,
credential_type text,
project_id wt_public_id not null,
constraint credential_vault_ssh_cert_library_store_id_name_uq
unique(store_id, name),
constraint credential_vault_ssh_cert_library_store_id_public_id_uq
unique(store_id, public_id),
constraint credential_library_fkey
foreign key (project_id, store_id, public_id, credential_type)
references credential_library (project_id, store_id, public_id, credential_type)
on delete cascade
on update cascade,
constraint credential_vault_ssh_cert_valid_key_type_key_bits_fkey
foreign key (key_type, key_bits)
references credential_vault_ssh_cert_valid_key_type_key_bits(key_type, key_bits)
);
comment on table credential_vault_ssh_cert_library is
'credential_vault_ssh_cert_library a credential library that issues credentials from a vault ssh secret backend.';
create function default_ssh_certificate_credential_type() returns trigger
as $$
begin
if new.credential_type is distinct from 'ssh_certificate' then
raise warning 'credential_vault_ssh_cert_library only supports ssh_certificate credentials';
new.credential_type = 'ssh_certificate';
end if;
return new;
end;
$$ language plpgsql;
comment on function default_ssh_certificate_credential_type is
'default_ssh_certificate_credential_type ensures the credential_type is set to ssh_certificate';
create trigger default_ssh_certificate_credential_type before insert on credential_vault_ssh_cert_library
for each row execute procedure default_ssh_certificate_credential_type();
create trigger insert_credential_library_subtype before insert on credential_vault_ssh_cert_library
for each row execute procedure insert_credential_library_subtype();
create trigger default_create_time_column before insert on credential_vault_ssh_cert_library
for each row execute procedure default_create_time();
create trigger delete_credential_library_subtype after delete on credential_vault_ssh_cert_library
for each row execute procedure delete_credential_library_subtype();
create trigger immutable_columns before update on credential_vault_ssh_cert_library
for each row execute procedure immutable_columns('public_id', 'store_id', 'project_id', 'credential_type', 'create_time');
create trigger update_time_column before update on credential_vault_ssh_cert_library
for each row execute procedure update_time_column();
create trigger update_version_column after update on credential_vault_ssh_cert_library
for each row execute procedure update_version_column();
create trigger before_insert_credential_vault_library before insert on credential_vault_ssh_cert_library
for each row execute procedure before_insert_credential_vault_library();
insert into oplog_ticket (name, version)
values
('credential_vault_ssh_cert_library', 1);
commit;
@@ -0,0 +1,111 @@
begin;
-- Replaces view from 56/02_add_data_key_foreign_key_references.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_library_username_password_mapping_override
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_library_ssh_private_key_mapping_override
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
coalesce(upasso.username_attribute,sshpk.username_attribute)
as username_attribute,
upasso.password_attribute as password_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
'generic' as cred_lib_type -- used to switch on
from credential_vault_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
library.key_type as key_type,
library.key_bits as key_bits,
library.username as username,
library.ttl as ttl,
library.key_id as key_id,
library.critical_options as critical_options,
library.extensions as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
null as username_attribute,
null as password_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ssh-signed-cert' as cred_lib_type -- used to switch on
from credential_vault_ssh_cert_library library
join credential_vault_store_client store
on library.store_id = store.public_id;
comment on view credential_vault_library_issue_credentials is
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
commit;
@@ -0,0 +1,254 @@
begin;
alter table wh_credential_dimension
add column credential_library_username text,
add column credential_library_key_type_and_bits text
;
update wh_credential_dimension set
credential_library_username = 'Not Applicable',
credential_library_key_type_and_bits = 'Not Applicable';
update wh_credential_dimension set
credential_library_vault_http_request_body = 'Not Applicable'
where
credential_library_vault_http_method = 'GET'
and
credential_library_vault_http_request_body = 'None';
alter table wh_credential_dimension
alter column credential_library_username type wh_dim_text,
alter column credential_library_key_type_and_bits type wh_dim_text
;
update wh_credential_dimension set
credential_library_type = 'vault generic credential library'
where
credential_library_type = 'vault credential library';
-- The whx_credential_dimension_source view shows the current values in the
-- operational tables of the credential dimension.
-- Replaces whx_credential_dimension_source defined in 44/03_targets.up.sql
drop view whx_credential_dimension_source;
create view whx_credential_dimension_source as
with vault_generic_library as (
select vcl.public_id as public_id,
'vault generic credential library' as type,
coalesce(vcl.name, 'None') as name,
coalesce(vcl.description, 'None') as description,
vcl.vault_path as vault_path,
vcl.http_method as http_method,
case
when vcl.http_method = 'GET' then 'Not Applicable'
else coalesce(vcl.http_request_body::text, 'None')
end as http_request_body,
'Not Applicable' as username,
'Not Applicable' as key_type_and_bits
from credential_vault_library as vcl
),
vault_ssh_cert_library as (
select vsccl.public_id as public_id,
'vault ssh certificate credential library' as type,
coalesce(vsccl.name, 'None') as name,
coalesce(vsccl.description, 'None') as description,
vsccl.vault_path as vault_path,
'Not Applicable' as http_method,
'Not Applicable' as http_request_body,
vsccl.username as username,
case
when vsccl.key_type = 'ed25519' then vsccl.key_type
else vsccl.key_type || '-' || vsccl.key_bits::text
end as key_type_and_bits
from credential_vault_ssh_cert_library as vsccl
),
final as (
select s.public_id as session_id,
scd.credential_purpose as credential_purpose,
cl.public_id as credential_library_id,
coalesce(vcl.type, vsccl.type) as credential_library_type,
coalesce(vcl.name, vsccl.name) as credential_library_name,
coalesce(vcl.description, vsccl.description) as credential_library_description,
coalesce(vcl.vault_path, vsccl.vault_path) as credential_library_vault_path,
coalesce(vcl.http_method, vsccl.http_method) as credential_library_vault_http_method,
coalesce(vcl.http_request_body, vsccl.http_request_body) as credential_library_vault_http_request_body,
coalesce(vcl.username, vsccl.username) as credential_library_username,
coalesce(vcl.key_type_and_bits, vsccl.key_type_and_bits) as credential_library_key_type_and_bits,
cs.public_id as credential_store_id,
case
when vcs is null then 'None'
else 'vault credential store'
end as credential_store_type,
coalesce(vcs.name, 'None') as credential_store_name,
coalesce(vcs.description, 'None') as credential_store_description,
coalesce(vcs.namespace, 'None') as credential_store_vault_namespace,
coalesce(vcs.vault_address, 'None') as credential_store_vault_address,
t.public_id as target_id,
'tcp target' as target_type,
coalesce(tt.name, 'None') as target_name,
coalesce(tt.description, 'None') as target_description,
coalesce(tt.default_port, 0) as target_default_port_number,
tt.session_max_seconds as target_session_max_seconds,
tt.session_connection_limit as target_session_connection_limit,
p.public_id as project_id,
coalesce(p.name, 'None') as project_name,
coalesce(p.description, 'None') as project_description,
o.public_id as organization_id,
coalesce(o.name, 'None') as organization_name,
coalesce(o.description, 'None') as organization_description
from session_credential_dynamic as scd
join session as s on scd.session_id = s.public_id
join credential_library as cl on scd.library_id = cl.public_id
join credential_store as cs on cl.store_id = cs.public_id
join target as t on s.target_id = t.public_id
join iam_scope as p on p.public_id = t.project_id and p.type = 'project'
join iam_scope as o on p.parent_id = o.public_id and o.type = 'org'
left join vault_generic_library as vcl on cl.public_id = vcl.public_id
left join vault_ssh_cert_library as vsccl on cl.public_id = vsccl.public_id
left join credential_vault_store as vcs on cs.public_id = vcs.public_id
left join target_tcp as tt on t.public_id = tt.public_id
)
select session_id,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_library_username,
credential_library_key_type_and_bits,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from final;
-- Replaces view in 16/02_wh_credential_dimension.up.sql
drop view whx_credential_dimension_target;
create view whx_credential_dimension_target as
select key,
credential_purpose,
credential_library_id,
credential_library_type,
credential_library_name,
credential_library_description,
credential_library_vault_path,
credential_library_vault_http_method,
credential_library_vault_http_request_body,
credential_library_username,
credential_library_key_type_and_bits,
credential_store_id,
credential_store_type,
credential_store_name,
credential_store_description,
credential_store_vault_namespace,
credential_store_vault_address,
target_id,
target_type,
target_name,
target_description,
target_default_port_number,
target_session_max_seconds,
target_session_connection_limit,
project_id,
project_name,
project_description,
organization_id,
organization_name,
organization_description
from wh_credential_dimension
where current_row_indicator = 'Current'
;
-- Replaces function in 16/03_wh_credential_dimension.up.sql
drop function wh_upsert_credential_dimension;
create function wh_upsert_credential_dimension(p_session_id wt_public_id, p_library_id wt_public_id, p_credential_purpose wh_dim_text) returns wh_dim_key
as $$
declare
src whx_credential_dimension_target%rowtype;
target whx_credential_dimension_target%rowtype;
new_row wh_credential_dimension%rowtype;
t_id wt_public_id;
begin
select s.target_id into strict t_id
from session as s
where s.public_id = p_session_id;
select * into target
from whx_credential_dimension_target as t
where t.credential_library_id = p_library_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
select
target.key, t.credential_purpose,
t.credential_library_id, t.credential_library_type, t.credential_library_name, t.credential_library_description, t.credential_library_vault_path, t.credential_library_vault_http_method, t.credential_library_vault_http_request_body,
t.credential_library_username, t.credential_library_key_type_and_bits,
t.credential_store_id, t.credential_store_type, t.credential_store_name, t.credential_store_description, t.credential_store_vault_namespace, t.credential_store_vault_address,
t.target_id, t.target_type, t.target_name, t.target_description, t.target_default_port_number, t.target_session_max_seconds, t.target_session_connection_limit,
t.project_id, t.project_name, t.project_description,
t.organization_id, t.organization_name, t.organization_description
into src
from whx_credential_dimension_source as t
where t.credential_library_id = p_library_id
and t.session_id = p_session_id
and t.target_id = t_id
and t.credential_purpose = p_credential_purpose;
if src is distinct from target then
update wh_credential_dimension
set current_row_indicator = 'Expired',
row_expiration_time = current_timestamp
where credential_library_id = p_library_id
and target_id = t_id
and credential_purpose = p_credential_purpose
and current_row_indicator = 'Current';
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_library_username, credential_library_key_type_and_bits,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
select credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_library_username, credential_library_key_type_and_bits,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
'Current', current_timestamp, 'infinity'::timestamptz
from whx_credential_dimension_source
where credential_library_id = p_library_id
and session_id = p_session_id
and target_id = t_id
and credential_purpose = p_credential_purpose
returning * into new_row;
return new_row.key;
end if;
return target.key;
end
$$ language plpgsql;
commit;
@@ -315,7 +315,9 @@ begin;
(project_id, public_id, name)
values
('p____bwidget', 't_________wb', 'Big Widget Target'),
('p____swidget', 't_________ws', 'Small Widget Target');
('p____swidget', 't_________ws', 'Small Widget Target'),
('p____swidget', 't________ws2', 'Small Widget Target 2'),
('p____swidget', 't________ws3', 'Small Widget Target 3');
insert into target_host_set
(project_id, target_id, host_set_id)
@@ -327,7 +329,9 @@ begin;
('p____bwidget', 't_________wb', 's___1wb-plghs'),
('p____bwidget', 't_________wb', 's___2wb-plghs'),
('p____swidget', 't_________ws', 's___1ws-plghs'),
('p____swidget', 't_________ws', 's___2ws-plghs');
('p____swidget', 't_________ws', 's___2ws-plghs'),
('p____swidget', 't________ws2', 's___1ws-sths'),
('p____swidget', 't________ws3', 's___1ws-sths');
end;
$$ language plpgsql;
@@ -336,9 +340,10 @@ begin;
as $$
begin
insert into credential_vault_store
(project_id, public_id, name, description, vault_address, namespace)
(project_id, public_id, name, description, vault_address, namespace)
values
('p____bwidget', 'vs_______wvs', 'widget vault store', 'None', 'https://vault.widget', 'default');
('p____bwidget', 'vs_______wvs', 'widget vault store', 'None', 'https://vault.widget', 'default'),
('p____swidget', 'vs______swvs', 'small widget vault store', 'None', 'https://small.vault.widget', 'default');
insert into credential_vault_token
(store_id, key_id, status, token_hmac, token, last_renewal_time, expiration_time)
@@ -401,13 +406,23 @@ begin;
values
('vl______wvl12', 'my_username', 'my_private_key', 'my_passphrase');
insert into credential_vault_ssh_cert_library
(store_id, public_id, name, description, vault_path, username, key_type, key_bits)
values
('vs______swvs', 'vscl____wvl1', 'widget ssh admin cert', 'None', '/ssh/issue/admin', 'admin', 'rsa', 4096),
('vs______swvs', 'vscl____wvl2', 'widget ssh ecdsa', 'None', '/ssh/sign/user', 'user', 'ecdsa', 521),
('vs______swvs', 'vscl____wvl3', 'widget ssh ed25519', 'None', '/ssh/sign/user', 'user', 'ed25519', 0);
insert into target_credential_library
(project_id, target_id, credential_library_id, credential_purpose)
values
('p____bwidget', 't_________wb', 'vl______wvl1', 'brokered'),
('p____bwidget', 't_________wb', 'vl______wvl2', 'brokered'),
('p____bwidget', 't_________wb', 'vl______wvl3', 'brokered'),
('p____bwidget', 't_________wb', 'vl______wvl3', 'injected_application');
('p____bwidget', 't_________wb', 'vl______wvl3', 'injected_application'),
('p____bwidget', 't_________ws', 'vscl____wvl1', 'injected_application'),
('p____bwidget', 't________ws2', 'vscl____wvl2', 'injected_application'),
('p____bwidget', 't________ws3', 'vscl____wvl3', 'injected_application');
insert into credential_static_store
(project_id, public_id, name, description)
@@ -432,9 +447,9 @@ create function _wtt_load_widgets_sessions() returns void
as $$
begin
insert into session
( project_id , target_id , host_set_id , host_id , user_id , auth_token_id , certificate , endpoint , public_id)
(project_id, target_id, host_set_id, host_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____bwidget' , 't_________wb' , 's___1wb-sths' , 'h_____wb__01' , 'u_____warren' , 'tok___warren' , 'abc'::bytea , 'ep1' , 's1____warren');
('p____swidget', 't_________wb', 's___1wb-sths', 'h_____wb__01', 'u_____warren', 'tok___warren', 'abc'::bytea, 'ep1', 's1____warren');
end;
$$ language plpgsql;
@@ -0,0 +1,148 @@
begin;
select plan(16);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate default values
prepare insert_valid as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc1', '/ssh/sign/foo', 'bar', 'ed25519', 0),
('vs_______wvs', 'vl______vsc2', '/ssh/sign/foo', 'bar', 'ecdsa', 256),
('vs_______wvs', 'vl______vsc3', '/ssh/sign/foo', 'bar', 'ecdsa', 384),
('vs_______wvs', 'vl______vsc4', '/ssh/sign/foo', 'bar', 'ecdsa', 521),
('vs_______wvs', 'vl______vsc5', '/ssh/sign/foo', 'bar', 'rsa', 2048),
('vs_______wvs', 'vl______vsc6', '/ssh/sign/foo', 'bar', 'rsa', 3072),
('vs_______wvs', 'vl______vsc7', '/ssh/sign/foo', 'bar', 'rsa', 4096);
prepare select_vault_ssh_cert_libraries as
select public_id::text, store_id::text, name::text, description::text, vault_path, username, key_type, key_bits, ttl, key_id, critical_options, extensions, credential_type, project_id::text
from credential_vault_ssh_cert_library
where public_id like 'vl______vsc%'
order by public_id;
prepare select_libraries as
select public_id::text, store_id::text, credential_type, project_id::text
from credential_library
where public_id in ('vl______vsc1', 'vl______vsc3')
order by public_id;
select lives_ok('insert_valid');
select results_eq(
'select_vault_ssh_cert_libraries',
$$VALUES
('vl______vsc1', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ed25519', 0, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc2', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 256, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc3', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 384, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc4', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 521, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc5', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 2048, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc6', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 3072, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc7', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 4096, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget')$$
);
prepare insert_invalid_key_type as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'unknown', 256);
select throws_ok('insert_invalid_key_type', 'insert or update on table "credential_vault_ssh_cert_library" violates foreign key constraint "credential_vault_ssh_cert_valid_key_type_key_bits_fkey"');
prepare insert_invalid_key_bits as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'rsa', 99);
select throws_ok('insert_invalid_key_bits', 'insert or update on table "credential_vault_ssh_cert_library" violates foreign key constraint "credential_vault_ssh_cert_valid_key_type_key_bits_fkey"');
prepare insert_invalid_rsa_key_type_bits_combo as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'rsa', 384);
select throws_ok('insert_invalid_rsa_key_type_bits_combo', 'insert or update on table "credential_vault_ssh_cert_library" violates foreign key constraint "credential_vault_ssh_cert_valid_key_type_key_bits_fkey"');
prepare insert_invalid_ecdsa_key_type_bits_combo as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'ecdsa', 2048);
select throws_ok('insert_invalid_ecdsa_key_type_bits_combo', 'insert or update on table "credential_vault_ssh_cert_library" violates foreign key constraint "credential_vault_ssh_cert_valid_key_type_key_bits_fkey"');
prepare insert_invalid_ed25519_key_type_bits_combo as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'ed25519', 2048);
select throws_ok('insert_invalid_ed25519_key_type_bits_combo', 'insert or update on table "credential_vault_ssh_cert_library" violates foreign key constraint "credential_vault_ssh_cert_valid_key_type_key_bits_fkey"');
prepare insert_invalid_vault_path as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/not-sign/foo', 'bar', 'ed25519', 0);
select throws_ok('insert_invalid_vault_path', 'new row for relation "credential_vault_ssh_cert_library" violates check constraint "vault_path_must_be_sign_or_issue"');
prepare insert_invalid_vault_path_empty as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '', 'bar', 'ed25519', 0);
select throws_ok('insert_invalid_vault_path', 'new row for relation "credential_vault_ssh_cert_library" violates check constraint "vault_path_must_be_sign_or_issue"');
prepare insert_invalid_username as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', '', 'ed25519', 0);
select throws_ok('insert_invalid_username', 'new row for relation "credential_vault_ssh_cert_library" violates check constraint "username_must_not_be_empty"');
prepare insert_invalid_credential_type as
insert into credential_vault_ssh_cert_library
(store_id, public_id, vault_path, username, credential_type, key_type, key_bits)
values
('vs_______wvs', 'vl______vsc8', '/ssh/sign/foo', 'bar', 'username_password', 'ed25519', 0);
select lives_ok('insert_invalid_credential_type');
select results_eq(
'select_vault_ssh_cert_libraries',
$$VALUES
('vl______vsc1', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ed25519', 0, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc2', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 256, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc3', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 384, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc4', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 521, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc5', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 2048, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc6', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 3072, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc7', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 4096, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc8', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ed25519', 0, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget')$$
);
select results_eq(
'select_libraries',
$$VALUES
('vl______vsc1', 'vs_______wvs', 'ssh_certificate', 'p____bwidget'),
('vl______vsc3', 'vs_______wvs', 'ssh_certificate', 'p____bwidget')$$
);
prepare delete_ssh_cred_library as
delete from credential_vault_ssh_cert_library where public_id = 'vl______vsc3';
select lives_ok('delete_ssh_cred_library');
select results_eq(
'select_vault_ssh_cert_libraries',
$$VALUES
('vl______vsc1', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ed25519', 0, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc2', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 256, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc4', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ecdsa', 521, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc5', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 2048, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc6', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 3072, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc7', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'rsa', 4096, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget'),
('vl______vsc8', 'vs_______wvs', null, null, '/ssh/sign/foo', 'bar', 'ed25519', 0, null, null, null::bytea, null::bytea, 'ssh_certificate', 'p____bwidget')$$
);
select results_eq(
'select_libraries',
$$VALUES
('vl______vsc1', 'vs_______wvs', 'ssh_certificate', 'p____bwidget')$$
);
rollback;
@@ -3,20 +3,22 @@ begin;
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_library_username, credential_library_key_type_and_bits,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
) values (
'brokered',
'vl______wvl1', 'vault credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
'vl______wvl1', 'vault generic credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'Not Applicable', 'Not Applicable',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
);
update wh_credential_dimension
@@ -30,20 +32,22 @@ begin;
insert into wh_credential_dimension (
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_library_username, credential_library_key_type_and_bits,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
) values (
'brokered',
'vl______wvl1', 'vault credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
'vl______wvl1', 'vault generic credential library', 'gidget vault library', 'None', '/secrets', 'GET', '\x4e6f6e65',
'Not Applicable', 'Not Applicable',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'default', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', current_timestamp, 'infinity'::timestamptz
);
select is(count(*), 2::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
@@ -0,0 +1,56 @@
-- ssh_certificate_cred_library tests the wh_credential_dimension when
-- sessions are created with targets that use a vault ssh cert credential library with ecdsa key type
begin;
select plan(3);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert first session, should result in a new credentials dimension
insert into session
( project_id, target_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____swidget', 't________ws2', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's2____walter');
insert into session_host_set_host
(session_id, host_set_id, host_id)
values
('s2____walter', 's___1ws-sths', 'h_____ws__01');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s2____walter', 'vscl____wvl2', null, 'injected_application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
prepare select_credential_dimensions as
select
credential_purpose::text,
credential_library_id::text, credential_library_type::text, credential_library_name::text, credential_library_description::text, credential_library_vault_path::text, credential_library_vault_http_method::text, credential_library_vault_http_request_body::text,
credential_library_username::text, credential_library_key_type_and_bits::text,
credential_store_id::text, credential_store_type::text, credential_store_name::text, credential_store_description::text, credential_store_vault_namespace::text, credential_store_vault_address::text,
target_id::text, target_type::text, target_name::text, target_description::text, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id::text, project_name::text, project_description::text,
organization_id::text, organization_name::text, organization_description::text,
current_row_indicator::text
from wh_credential_dimension
where organization_id = 'o_____widget';
select results_eq(
'select_credential_dimensions',
$$VALUES
(
'injected_application',
'vscl____wvl2', 'vault ssh certificate credential library', 'widget ssh ecdsa', 'None', '/ssh/sign/user', 'Not Applicable', 'Not Applicable',
'user', 'ecdsa-521',
'vs______swvs', 'vault credential store', 'small widget vault store', 'None', 'default', 'https://small.vault.widget',
't________ws2', 'tcp target', 'Small Widget Target 2', 'None', 0, 28800, -1,
'p____swidget', 'Small Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current'
)$$
);
select * from finish();
rollback;
@@ -0,0 +1,56 @@
-- ssh_certificate_cred_library tests the wh_credential_dimension when
-- sessions are created with targets that use a vault ssh cert credential library
begin;
select plan(3);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert first session, should result in a new credentials dimension
insert into session
( project_id, target_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____swidget', 't________ws3', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's3____walter');
insert into session_host_set_host
(session_id, host_set_id, host_id)
values
('s3____walter', 's___1ws-sths', 'h_____ws__01');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s3____walter', 'vscl____wvl3', null, 'injected_application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
prepare select_credential_dimensions as
select
credential_purpose::text,
credential_library_id::text, credential_library_type::text, credential_library_name::text, credential_library_description::text, credential_library_vault_path::text, credential_library_vault_http_method::text, credential_library_vault_http_request_body::text,
credential_library_username::text, credential_library_key_type_and_bits::text,
credential_store_id::text, credential_store_type::text, credential_store_name::text, credential_store_description::text, credential_store_vault_namespace::text, credential_store_vault_address::text,
target_id::text, target_type::text, target_name::text, target_description::text, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id::text, project_name::text, project_description::text,
organization_id::text, organization_name::text, organization_description::text,
current_row_indicator::text
from wh_credential_dimension
where organization_id = 'o_____widget';
select results_eq(
'select_credential_dimensions',
$$VALUES
(
'injected_application',
'vscl____wvl3', 'vault ssh certificate credential library', 'widget ssh ed25519', 'None', '/ssh/sign/user', 'Not Applicable', 'Not Applicable',
'user', 'ed25519',
'vs______swvs', 'vault credential store', 'small widget vault store', 'None', 'default', 'https://small.vault.widget',
't________ws3', 'tcp target', 'Small Widget Target 3', 'None', 0, 28800, -1,
'p____swidget', 'Small Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current'
)$$
);
select * from finish();
rollback;
@@ -0,0 +1,56 @@
-- ssh_certificate_cred_library tests the wh_credential_dimension when
-- sessions are created with targets that use a vault ssh cert credential library with rsa key type
begin;
select plan(3);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- ensure no existing dimensions
select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
-- insert first session, should result in a new credentials dimension
insert into session
( project_id, target_id, user_id, auth_token_id, certificate, endpoint, public_id)
values
('p____swidget', 't_________ws', 'u_____walter', 'tok___walter', 'abc'::bytea, 'ep1', 's1____walter');
insert into session_host_set_host
(session_id, host_set_id, host_id)
values
('s1____walter', 's___1ws-sths', 'h_____ws__01');
insert into session_credential_dynamic
( session_id, library_id, credential_id, credential_purpose)
values
('s1____walter', 'vscl____wvl1', null, 'injected_application');
select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget';
prepare select_credential_dimensions as
select
credential_purpose::text,
credential_library_id::text, credential_library_type::text, credential_library_name::text, credential_library_description::text, credential_library_vault_path::text, credential_library_vault_http_method::text, credential_library_vault_http_request_body::text,
credential_library_username::text, credential_library_key_type_and_bits::text,
credential_store_id::text, credential_store_type::text, credential_store_name::text, credential_store_description::text, credential_store_vault_namespace::text, credential_store_vault_address::text,
target_id::text, target_type::text, target_name::text, target_description::text, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id::text, project_name::text, project_description::text,
organization_id::text, organization_name::text, organization_description::text,
current_row_indicator::text
from wh_credential_dimension
where organization_id = 'o_____widget';
select results_eq(
'select_credential_dimensions',
$$VALUES
(
'injected_application',
'vscl____wvl1', 'vault ssh certificate credential library', 'widget ssh admin cert', 'None', '/ssh/issue/admin', 'Not Applicable', 'Not Applicable',
'admin', 'rsa-4096',
'vs______swvs', 'vault credential store', 'small widget vault store', 'None', 'default', 'https://small.vault.widget',
't_________ws', 'tcp target', 'Small Widget Target', 'None', 0, 28800, -1,
'p____swidget', 'Small Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current'
)$$
);
select * from finish();
rollback;
@@ -18,36 +18,39 @@ begin;
select is(s.*, row(
's1____walter',
'brokered', -- credential_purpose,
'vl______wvl1', -- credential_library_id,
'vault credential library', -- credential_library_type,
'widget vault library', -- credential_library_name,
'None', -- credential_library_description,
'/secrets', -- credential_library_vault_path,
'GET', -- credential_library_vault_http_method,
'None', -- credential_library_vault_http_request_body,
'brokered', -- credential_purpose,
'vl______wvl1', -- credential_library_id,
'vault generic credential library', -- credential_library_type,
'widget vault library', -- credential_library_name,
'None', -- credential_library_description,
'/secrets', -- credential_library_vault_path,
'GET', -- credential_library_vault_http_method,
'Not Applicable', -- credential_library_vault_http_request_body,
'vs_______wvs', -- credential_store_id,
'vault credential store', -- credential_store_type,
'widget vault store', -- credential_store_name,
'None', -- credential_store_description,
'default', -- credential_store_vault_namespace,
'https://vault.widget', -- credential_store_vault_address,
'Not Applicable', -- credential_library_username,
'Not Applicable', -- credential_library_key_type_and_bits,
't_________wb', -- target_id,
'tcp target', -- target_type,
'Big Widget Target', -- target_name,
'None', -- target_description,
0, -- target_default_port_number,
28800, -- target_session_max_seconds,
-1, -- target_session_connection_limit,
'vs_______wvs', -- credential_store_id,
'vault credential store', -- credential_store_type,
'widget vault store', -- credential_store_name,
'None', -- credential_store_description,
'default', -- credential_store_vault_namespace,
'https://vault.widget', -- credential_store_vault_address,
'p____bwidget', -- project_id,
'Big Widget Factory', -- project_name,
'None', -- project_description,
'o_____widget', -- organization_id,
'Widget Inc', -- organization_name,
'None' -- organization_description
't_________wb', -- target_id,
'tcp target', -- target_type,
'Big Widget Target', -- target_name,
'None', -- target_description,
0, -- target_default_port_number,
28800, -- target_session_max_seconds,
-1, -- target_session_connection_limit,
'p____bwidget', -- project_id,
'Big Widget Factory', -- project_name,
'None', -- project_description,
'o_____widget', -- organization_id,
'Widget Inc', -- organization_name,
'None' -- organization_description
)::whx_credential_dimension_source)
from whx_credential_dimension_source as s
where s.target_id = 't_________wb';
@@ -8,33 +8,36 @@ begin;
(
key,
credential_purpose,
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
credential_library_id, credential_library_type, credential_library_name, credential_library_description, credential_library_vault_path, credential_library_vault_http_method, credential_library_vault_http_request_body,
credential_library_username, credential_library_key_type_and_bits,
credential_store_id, credential_store_type, credential_store_name, credential_store_description, credential_store_vault_namespace, credential_store_vault_address,
target_id, target_type, target_name, target_description, target_default_port_number, target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time
)
values
(
'wcd________1',
'brokered',
'vl_______wvl', 'vault credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', '2021-07-21T12:01'::timestamptz, 'infinity'::timestamptz
'vl_______wvl', 'vault generic credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'Not Applicable', 'Not Applicable',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None',
'Current', '2021-07-21T12:01'::timestamptz, 'infinity'::timestamptz
);
select is(t.*, row(
'wcd________1',
'brokered',
'vl_______wvl', 'vault credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None'
'vl_______wvl', 'vault generic credential library', 'widget vault library', 'None', '/secrets', 'GET', 'None',
'Not Applicable', 'Not Applicable',
'vs_______wvs', 'vault credential store', 'widget vault store', 'None', 'blue', 'https://vault.widget',
't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1,
'p____bwidget', 'Big Widget Factory', 'None',
'o_____widget', 'Widget Inc', 'None'
)::whx_credential_dimension_target)
from whx_credential_dimension_target as t
where t.target_id = 't_________wb';
@@ -29,6 +29,7 @@ type Credential struct {
//
// *Credential_UsernamePassword
// *Credential_SshPrivateKey
// *Credential_SshCertificate
Credential isCredential_Credential `protobuf_oneof:"credential"`
}
@@ -85,6 +86,13 @@ func (x *Credential) GetSshPrivateKey() *SshPrivateKey {
return nil
}
func (x *Credential) GetSshCertificate() *SshCertificate {
if x, ok := x.GetCredential().(*Credential_SshCertificate); ok {
return x.SshCertificate
}
return nil
}
type isCredential_Credential interface {
isCredential_Credential()
}
@@ -97,10 +105,16 @@ type Credential_SshPrivateKey struct {
SshPrivateKey *SshPrivateKey `protobuf:"bytes,3,opt,name=ssh_private_key,json=sshPrivateKey,proto3,oneof"`
}
type Credential_SshCertificate struct {
SshCertificate *SshCertificate `protobuf:"bytes,4,opt,name=ssh_certificate,json=sshCertificate,proto3,oneof"`
}
func (*Credential_UsernamePassword) isCredential_Credential() {}
func (*Credential_SshPrivateKey) isCredential_Credential() {}
func (*Credential_SshCertificate) isCredential_Credential() {}
// UsernamePassword is a credential containing a username and a password.
type UsernamePassword struct {
state protoimpl.MessageState
@@ -227,6 +241,74 @@ func (x *SshPrivateKey) GetPrivateKeyPassphrase() string {
return ""
}
// SshCertificate is a credential containing a username, private key, and
// client certificate.
type SshCertificate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The username of the credential
Username string `protobuf:"bytes,10,opt,name=username,proto3" json:"username,omitempty"` // @gotags: `class:"public"`
// The private key of the credential
PrivateKey string `protobuf:"bytes,20,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` // @gotags: `class:"secret"`
// The client certificate signed by a CA to establish trust of the private key.
Certificate string `protobuf:"bytes,30,opt,name=certificate,proto3" json:"certificate,omitempty"` // @gotags: `class:"public"`
}
func (x *SshCertificate) Reset() {
*x = SshCertificate{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_servers_services_v1_credential_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SshCertificate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SshCertificate) ProtoMessage() {}
func (x *SshCertificate) ProtoReflect() protoreflect.Message {
mi := &file_controller_servers_services_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 SshCertificate.ProtoReflect.Descriptor instead.
func (*SshCertificate) Descriptor() ([]byte, []int) {
return file_controller_servers_services_v1_credential_proto_rawDescGZIP(), []int{3}
}
func (x *SshCertificate) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *SshCertificate) GetPrivateKey() string {
if x != nil {
return x.PrivateKey
}
return ""
}
func (x *SshCertificate) GetCertificate() string {
if x != nil {
return x.Certificate
}
return ""
}
var File_controller_servers_services_v1_credential_proto protoreflect.FileDescriptor
var file_controller_servers_services_v1_credential_proto_rawDesc = []byte{
@@ -235,7 +317,7 @@ var file_controller_servers_services_v1_credential_proto_rawDesc = []byte{
0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x1e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x22, 0xe9, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x31, 0x22, 0xc4, 0x02, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x12, 0x5f, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73,
@@ -247,28 +329,41 @@ var file_controller_servers_services_v1_credential_proto_rawDesc = []byte{
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x73, 0x68, 0x50,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x73, 0x68,
0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x0d,
0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4a, 0x0a,
0x10, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x0d, 0x53, 0x73,
0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75,
0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x73, 0x73,
0x68, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4a, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72,
0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08,
0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x0d, 0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b,
0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x18, 0x1e, 0x20,
0x01, 0x28, 0x09, 0x52, 0x14, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50,
0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x22, 0x6f, 0x0a, 0x0e, 0x53, 0x73, 0x68,
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61,
0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61,
0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x42, 0x51,
0x5a, 0x4f, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x42, 0x51, 0x5a, 0x4f, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -283,20 +378,22 @@ func file_controller_servers_services_v1_credential_proto_rawDescGZIP() []byte {
return file_controller_servers_services_v1_credential_proto_rawDescData
}
var file_controller_servers_services_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_controller_servers_services_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_controller_servers_services_v1_credential_proto_goTypes = []interface{}{
(*Credential)(nil), // 0: controller.servers.services.v1.Credential
(*UsernamePassword)(nil), // 1: controller.servers.services.v1.UsernamePassword
(*SshPrivateKey)(nil), // 2: controller.servers.services.v1.SshPrivateKey
(*SshCertificate)(nil), // 3: controller.servers.services.v1.SshCertificate
}
var file_controller_servers_services_v1_credential_proto_depIdxs = []int32{
1, // 0: controller.servers.services.v1.Credential.username_password:type_name -> controller.servers.services.v1.UsernamePassword
2, // 1: controller.servers.services.v1.Credential.ssh_private_key:type_name -> controller.servers.services.v1.SshPrivateKey
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
3, // 2: controller.servers.services.v1.Credential.ssh_certificate:type_name -> controller.servers.services.v1.SshCertificate
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_controller_servers_services_v1_credential_proto_init() }
@@ -341,10 +438,23 @@ func file_controller_servers_services_v1_credential_proto_init() {
return nil
}
}
file_controller_servers_services_v1_credential_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SshCertificate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_controller_servers_services_v1_credential_proto_msgTypes[0].OneofWrappers = []interface{}{
(*Credential_UsernamePassword)(nil),
(*Credential_SshPrivateKey)(nil),
(*Credential_SshCertificate)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -352,7 +462,7 @@ func file_controller_servers_services_v1_credential_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_servers_services_v1_credential_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
@@ -17,10 +17,7 @@ message CredentialLibrary {
string id = 10; // @gotags: `class:"public"`
// The ID of the Credential Store of which this Credential Library is a part.
string credential_store_id = 20 [
json_name = "credential_store_id",
(controller.custom_options.v1.subtype_source_id) = true
]; // @gotags: `class:"public"`
string credential_store_id = 20 [json_name = "credential_store_id"]; // @gotags: `class:"public"`
// Output only. Scope information for this Credential Library.
resources.scopes.v1.ScopeInfo scope = 30;
@@ -67,6 +64,16 @@ message CredentialLibrary {
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "vault"
];
VaultSSHCertificateCredentialLibraryAttributes vault_ssh_certificate_credential_library_attributes = 102 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "vault-ssh-certificate"
];
VaultCredentialLibraryAttributes vault_generic_credential_library_attributes = 103 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "vault-generic"
];
}
// Output only. The available actions on this resource for this user.
@@ -116,3 +123,78 @@ message VaultCredentialLibraryAttributes {
}
]; // @gotags: `class:"secret"`
}
// The attributes of a vault SSH Certificate Credential Library.
message VaultSSHCertificateCredentialLibraryAttributes {
// The path in Vault to request credentials from.
google.protobuf.StringValue path = 10 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.path"
that: "VaultPath"
}
]; // @gotags: `class:"public"`
// The username to use when making an SSH connection.
google.protobuf.StringValue username = 20 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.username"
that: "Username"
}
]; // @gotags: `class:"sensitive"`
// The key type to use when generating an SSH private key.
google.protobuf.StringValue key_type = 30 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.key_type"
that: "KeyType"
}
]; // @gotags: `class:"public"`
// The number of bits to use to generate an SSH private key.
google.protobuf.UInt32Value key_bits = 40 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.key_bits"
that: "KeyBits"
}
]; // @gotags: `class:"public"`
// The requested time to live for the certificate.
google.protobuf.StringValue ttl = 50 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.ttl"
that: "Ttl"
}
]; // @gotags: `class:"public"`
// The key id that the created certificate should have.
google.protobuf.StringValue key_id = 60 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.key_id"
that: "KeyId"
}
]; // @gotags: `class:"public"`
// The critical options that the certificate should be signed for.
map<string, string> critical_options = 70 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.critical_options"
that: "CriticalOptions"
}
]; // @gotags: `class:"public"`
// The extensions that the certificate should be signed for.
map<string, string> extensions = 80 [
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.extensions"
that: "Extensions"
}
]; // @gotags: `class:"public"`
}
@@ -11,6 +11,7 @@ message Credential {
oneof credential {
UsernamePassword username_password = 2;
SshPrivateKey ssh_private_key = 3;
SshCertificate ssh_certificate = 4;
}
}
@@ -35,3 +36,16 @@ message SshPrivateKey {
// The optional passphrase of the private_key
string private_key_passphrase = 30; // @gotags: `class:"secret"`
}
// SshCertificate is a credential containing a username, private key, and
// client certificate.
message SshCertificate {
// The username of the credential
string username = 10; // @gotags: `class:"public"`
// The private key of the credential
string private_key = 20; // @gotags: `class:"secret"`
// The client certificate signed by a CA to establish trust of the private key.
string certificate = 30; // @gotags: `class:"public"`
}
@@ -265,6 +265,107 @@ message CredentialLibrary {
string credential_type = 11;
}
message SSHCertificateCredentialLibrary {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 1;
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// name is optional. If set, it must be unique within project_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {
this: "Name"
that: "name"
}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 5 [(custom_options.v1.mask_mapping) = {
this: "Description"
that: "description"
}];
// store_id of the owning vault credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string store_id = 6;
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
uint32 version = 7;
// vault_path is the path in Vault to request credentials from.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string vault_path = 8 [(custom_options.v1.mask_mapping) = {
this: "VaultPath"
that: "attributes.path"
}];
// username is the username to use when making an SSH connection.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string username = 9 [(custom_options.v1.mask_mapping) = {
this: "Username"
that: "attributes.username"
}];
// key_type specifies the key type to use when generating an SSH private key.
// Values must be "rsa", "ed25519", or "ecdsa".
// @inject_tag: `gorm:"not_null"`
string key_type = 10 [(custom_options.v1.mask_mapping) = {
this: "KeyType"
that: "attributes.key_type"
}];
// key_bits specifies the number of bits to use to generate an SSH private key.
// Not used if key_type is ed25519.
// @inject_tag: `gorm:"not_null"`
uint32 key_bits = 11 [(custom_options.v1.mask_mapping) = {
this: "KeyBits"
that: "attributes.key_bits"
}];
// ttl specifies the requested time to live for the certificate.
// @inject_tag: `gorm:"default:null"`
string ttl = 12 [(custom_options.v1.mask_mapping) = {
this: "Ttl"
that: "attributes.ttl"
}];
// key_id specifies the key id that the created certificate should have.
// @inject_tag: `gorm:"default:null"`
string key_id = 13 [(custom_options.v1.mask_mapping) = {
this: "KeyId"
that: "attributes.key_id"
}];
// critical_options specifies a map of the critical options that the certificate should be signed for.
// @inject_tag: `gorm:"default:null"`
string critical_options = 14 [(custom_options.v1.mask_mapping) = {
this: "CriticalOptions"
that: "attributes.critical_options"
}];
// extensions specifies a map of the extensions that the certificate should be signed for.
// @inject_tag: `gorm:"default:null"`
string extensions = 15 [(custom_options.v1.mask_mapping) = {
this: "Extensions"
that: "attributes.extensions"
}];
// credential_type is always ssh_certificate
// @inject_tag: `gorm:"default:null"`
string credential_type = 16;
}
message Credential {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
@@ -44,7 +44,7 @@ func TestList(t *testing.T) {
expected = append(expected, &credentiallibraries.CredentialLibrary{Name: fmt.Sprint(i), Attributes: map[string]any{"vault_path": "something"}})
}
cl, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName(expected[0].Name), credentiallibraries.WithVaultCredentialLibraryPath("something"))
cl, err := lClient.Create(tc.Context(), cs.Item.Type, cs.Item.Id, credentiallibraries.WithName(expected[0].Name), credentiallibraries.WithVaultCredentialLibraryPath("something"))
require.NoError(err)
expected[0] = cl.Item
@@ -53,7 +53,7 @@ func TestList(t *testing.T) {
assert.ElementsMatch(comparableSetSlice(expected[:1]), comparableSetSlice(ul.Items))
for i := 1; i < 10; i++ {
cl, err = lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName(expected[i].Name), credentiallibraries.WithVaultCredentialLibraryPath("something"))
cl, err = lClient.Create(tc.Context(), cs.Item.Type, cs.Item.Id, credentiallibraries.WithName(expected[i].Name), credentiallibraries.WithVaultCredentialLibraryPath("something"))
require.NoError(err)
expected[i] = cl.Item
}
@@ -115,8 +115,9 @@ func TestCrud(t *testing.T) {
lClient := credentiallibraries.NewClient(client)
r, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo"),
r, err := lClient.Create(tc.Context(), cs.Item.Type, cs.Item.Id, credentiallibraries.WithName("foo"),
credentiallibraries.WithVaultCredentialLibraryPath("something"))
require.Nil(err)
checkResource(t, "create", r.Item, err, "foo", 1)
r, err = lClient.Read(tc.Context(), r.Item.Id)
@@ -157,7 +158,7 @@ func TestErrors(t *testing.T) {
lClient := credentiallibraries.NewClient(client)
l, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo"),
l, err := lClient.Create(tc.Context(), cs.Item.Type, cs.Item.Id, credentiallibraries.WithName("foo"),
credentiallibraries.WithVaultCredentialLibraryPath("something"))
require.NoError(err)
assert.NotNil(l)
@@ -178,7 +179,7 @@ func TestErrors(t *testing.T) {
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
l, err = lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo"))
l, err = lClient.Create(tc.Context(), cs.Item.Type, cs.Item.Id, credentiallibraries.WithName("foo"))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
+4 -4
View File
@@ -86,11 +86,11 @@ func TestCredentialSourcesASD(t *testing.T) {
require.NotNil(csVault)
lClient := credentiallibraries.NewClient(client)
lib1, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1"))
lib1, err := lClient.Create(tc.Context(), csVault.Item.Type, csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1"))
require.NoError(err)
require.NotNil(lib1)
lib2, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2"))
lib2, err := lClient.Create(tc.Context(), csVault.Item.Type, csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2"))
require.NoError(err)
require.NotNil(lib2)
@@ -204,11 +204,11 @@ func TestDeprecatedCredentialSourcesASD(t *testing.T) {
require.NotNil(csVault)
lClient := credentiallibraries.NewClient(client)
lib1, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1"))
lib1, err := lClient.Create(tc.Context(), csVault.Item.Type, csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1"))
require.NoError(err)
require.NotNil(lib1)
lib2, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2"))
lib2, err := lClient.Create(tc.Context(), csVault.Item.Type, csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2"))
require.NoError(err)
require.NotNil(lib2)
+31
View File
@@ -1,5 +1,36 @@
all: test
REGISTRY_NAME ?= docker.io/hashicorp
TEST_VAULT_IMAGE_NAME=vault
TEST_VAULT_IMAGE ?= $(REGISTRY_NAME)/$(TEST_VAULT_IMAGE_NAME):1.12.2
TEST_VAULT_PORT ?= 8200
TEST_VAULT_CONTAINER_NAME ?= boundary-cli-tests-vault
VAULT_ADDR ?= "http://127.0.0.1:$(TEST_VAULT_PORT)"
VAULT_TOKEN ?= boundarytok
.PHONY: test-vault-up
test-vault-up:
@echo "Using image: $(TEST_VAULT_IMAGE)"
@echo " vault addr: $(VAULT_ADDR)"
@echo "vault token: $(VAULT_TOKEN)"
@docker run \
-d \
--name $(TEST_VAULT_CONTAINER_NAME) \
-e VAULT_DEV_ROOT_TOKEN_ID=$(VAULT_TOKEN) \
-p $(TEST_VAULT_PORT):8200 \
--cap-add IPC_LOCK \
$(TEST_VAULT_IMAGE)
@echo "Container name: $(TEST_VAULT_CONTAINER_NAME)"
@echo "Test vault available at: $(VAULT_ADDR)"
@echo "For vault logs run:"
@echo " docker logs $(TEST_VAULT_CONTAINER_NAME)"
.PHONY: test-vault-down
test-vault-down:
docker stop $(TEST_VAULT_CONTAINER_NAME) || true
docker rm -v $(TEST_VAULT_CONTAINER_NAME) || true
.PHONY: test
test:
./test.sh
@@ -0,0 +1,36 @@
load _authorized_actions
function create_vault_ssh_certificate_library() {
boundary credential-libraries \
create vault-ssh-certificate $@
}
function create_vault_generic_library() {
boundary credential-libraries \
create vault-generic $@
}
function create_vault_library() {
boundary credential-libraries \
create vault $@
}
function read_credential_library() {
boundary credential-libraries read -id $1 -format json
}
function delete_credential_library() {
boundary credential-libraries delete -id $1
}
function list_credential_libraries() {
local csid=$1
boundary credential-libraries list -credential-store-id $1 -format json
}
function credential_library_id() {
local name=$1
local csid=$2
strip $(list_credential_libraries $csid | jq -c ".items[] | select(.name | contains(\"$name\")) | .[\"id\"]")
}
@@ -10,6 +10,10 @@ function create_static_credential_store() {
-scope-id $sid
}
function create_vault_credential_store() {
boundary credential-stores create vault $@
}
function read_credential_store() {
boundary credential-stores read -id $1 -format json
}
+47
View File
@@ -0,0 +1,47 @@
function vault_write_boundary_policy() {
(
cat <<EOF
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}
path "sys/leases/renew" {
capabilities = ["update"]
}
path "sys/leases/revoke" {
capabilities = ["update"]
}
path "sys/capabilities-self" {
capabilities = ["update"]
}
EOF
) | vault policy write boundary-controller -
}
function create_vault_token() {
vault token create \
-format=json \
-no-default-policy=true \
-policy="boundary-controller" \
-orphan=true \
-period=2m \
-renewable=true | \
jq -r '.auth.client_token'
}
function skip_if_no_vault() {
if [[ -z $VAULT_ADDR ]] || [[ -z $VAULT_TOKEN ]]; then
skip "vault environment variables \$VAULT_ADDR and \$VAULT_TOKEN not set"
fi
}
+181
View File
@@ -0,0 +1,181 @@
#!/usr/bin/env bats
load _auth
load _helpers
load _vault
load _credential_stores
load _credential_libraries
export NEW_STORE='test_vault'
export NEW_VAULT_LIB="test_vault"
@test "boundary/login: can login as default user" {
run login $DEFAULT_LOGIN
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: setup vault-generic credential store $NEW_STORE in default project" {
skip_if_no_vault
vault_write_boundary_policy
local vault_token=$(create_vault_token)
run create_vault_credential_store \
-name $NEW_STORE -scope-id $DEFAULT_P_ID \
-vault-address $VAULT_ADDR \
-vault-token $vault_token
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can create $NEW_VAULT_LIB vault-generic library in credential store $NEW_STORE" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_generic_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /kv/secret \
-vault-http-method GET
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-generic libary" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_generic_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /kv/secret \
-vault-http-method GET
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-libraries: can read $NEW_VAULT_LIB vault-generic library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run read_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can delete $NEW_VAULT_LIB vault-generic library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run delete_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can create $NEW_VAULT_LIB vault library in credential store $NEW_STORE deprecated subcommand" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /kv/secret \
-vault-http-method GET
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault libary deprecated subcommand" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /kv/secret \
-vault-http-method GET
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-libraries: can read $NEW_VAULT_LIB vault library depcrecated subcommand" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run read_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can delete $NEW_VAULT_LIB vault library depcrecated subcommand" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run delete_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can create $NEW_VAULT_LIB vault-ssh-certificate library in credential store $NEW_STORE" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_ssh_certificate_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /ssh/sign/foo \
-username foo
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can not create already created $NEW_VAULT_LIB vault-ssh-certificate libary" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_vault_ssh_certificate_library \
-name $NEW_VAULT_LIB -credential-store-id $csid \
-vault-path /ssh/sign/foo \
-username foo
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-libraries: can read $NEW_VAULT_LIB vault-ssh-certificate library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run read_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credential-libraries: can delete $NEW_VAULT_LIB vault-ssh-certificate library" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local clid=$(credential_library_id $NEW_VAULT_LIB $csid)
run delete_credential_library $clid
echo "$output"
[ "$status" -eq 0 ]
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
# Note, deleting the cred store will revoke the vault token
@test "boundary/credential-stores: cleanup can delete $NEW_STORE vault store" {
skip_if_no_vault
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run delete_credential_store $csid
echo "$output"
[ "$status" -eq 0 ]
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@@ -55,6 +55,8 @@ type CredentialLibrary struct {
//
// *CredentialLibrary_Attributes
// *CredentialLibrary_VaultCredentialLibraryAttributes
// *CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes
// *CredentialLibrary_VaultGenericCredentialLibraryAttributes
Attrs isCredentialLibrary_Attrs `protobuf_oneof:"attrs"`
// Output only. The available actions on this resource for this user.
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty" class:"public"` // @gotags: `class:"public"`
@@ -180,6 +182,20 @@ func (x *CredentialLibrary) GetVaultCredentialLibraryAttributes() *VaultCredenti
return nil
}
func (x *CredentialLibrary) GetVaultSshCertificateCredentialLibraryAttributes() *VaultSSHCertificateCredentialLibraryAttributes {
if x, ok := x.GetAttrs().(*CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes); ok {
return x.VaultSshCertificateCredentialLibraryAttributes
}
return nil
}
func (x *CredentialLibrary) GetVaultGenericCredentialLibraryAttributes() *VaultCredentialLibraryAttributes {
if x, ok := x.GetAttrs().(*CredentialLibrary_VaultGenericCredentialLibraryAttributes); ok {
return x.VaultGenericCredentialLibraryAttributes
}
return nil
}
func (x *CredentialLibrary) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@@ -214,10 +230,23 @@ type CredentialLibrary_VaultCredentialLibraryAttributes struct {
VaultCredentialLibraryAttributes *VaultCredentialLibraryAttributes `protobuf:"bytes,101,opt,name=vault_credential_library_attributes,json=vaultCredentialLibraryAttributes,proto3,oneof"`
}
type CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes struct {
VaultSshCertificateCredentialLibraryAttributes *VaultSSHCertificateCredentialLibraryAttributes `protobuf:"bytes,102,opt,name=vault_ssh_certificate_credential_library_attributes,json=vaultSshCertificateCredentialLibraryAttributes,proto3,oneof"`
}
type CredentialLibrary_VaultGenericCredentialLibraryAttributes struct {
VaultGenericCredentialLibraryAttributes *VaultCredentialLibraryAttributes `protobuf:"bytes,103,opt,name=vault_generic_credential_library_attributes,json=vaultGenericCredentialLibraryAttributes,proto3,oneof"`
}
func (*CredentialLibrary_Attributes) isCredentialLibrary_Attrs() {}
func (*CredentialLibrary_VaultCredentialLibraryAttributes) isCredentialLibrary_Attrs() {}
func (*CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes) isCredentialLibrary_Attrs() {
}
func (*CredentialLibrary_VaultGenericCredentialLibraryAttributes) isCredentialLibrary_Attrs() {}
// The attributes of a vault typed Credential Library.
type VaultCredentialLibraryAttributes struct {
state protoimpl.MessageState
@@ -285,6 +314,118 @@ func (x *VaultCredentialLibraryAttributes) GetHttpRequestBody() *wrapperspb.Stri
return nil
}
// The attributes of a vault SSH Certificate Credential Library.
type VaultSSHCertificateCredentialLibraryAttributes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The path in Vault to request credentials from.
Path *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=path,proto3" json:"path,omitempty" class:"public"` // @gotags: `class:"public"`
// The username to use when making an SSH connection.
Username *wrapperspb.StringValue `protobuf:"bytes,20,opt,name=username,proto3" json:"username,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"`
// The key type to use when generating an SSH private key.
KeyType *wrapperspb.StringValue `protobuf:"bytes,30,opt,name=key_type,json=keyType,proto3" json:"key_type,omitempty" class:"public"` // @gotags: `class:"public"`
// The number of bits to use to generate an SSH private key.
KeyBits *wrapperspb.UInt32Value `protobuf:"bytes,40,opt,name=key_bits,json=keyBits,proto3" json:"key_bits,omitempty" class:"public"` // @gotags: `class:"public"`
// The requested time to live for the certificate.
Ttl *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=ttl,proto3" json:"ttl,omitempty" class:"public"` // @gotags: `class:"public"`
// The key id that the created certificate should have.
KeyId *wrapperspb.StringValue `protobuf:"bytes,60,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" class:"public"` // @gotags: `class:"public"`
// The critical options that the certificate should be signed for.
CriticalOptions map[string]string `protobuf:"bytes,70,rep,name=critical_options,json=criticalOptions,proto3" json:"critical_options,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3" class:"public"` // @gotags: `class:"public"`
// The extensions that the certificate should be signed for.
Extensions map[string]string `protobuf:"bytes,80,rep,name=extensions,proto3" json:"extensions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3" class:"public"` // @gotags: `class:"public"`
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) Reset() {
*x = VaultSSHCertificateCredentialLibraryAttributes{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VaultSSHCertificateCredentialLibraryAttributes) ProtoMessage() {}
func (x *VaultSSHCertificateCredentialLibraryAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_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 VaultSSHCertificateCredentialLibraryAttributes.ProtoReflect.Descriptor instead.
func (*VaultSSHCertificateCredentialLibraryAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescGZIP(), []int{2}
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetPath() *wrapperspb.StringValue {
if x != nil {
return x.Path
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetUsername() *wrapperspb.StringValue {
if x != nil {
return x.Username
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetKeyType() *wrapperspb.StringValue {
if x != nil {
return x.KeyType
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetKeyBits() *wrapperspb.UInt32Value {
if x != nil {
return x.KeyBits
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetTtl() *wrapperspb.StringValue {
if x != nil {
return x.Ttl
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetKeyId() *wrapperspb.StringValue {
if x != nil {
return x.KeyId
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetCriticalOptions() map[string]string {
if x != nil {
return x.CriticalOptions
}
return nil
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetExtensions() map[string]string {
if x != nil {
return x.Extensions
}
return nil
}
var File_controller_api_resources_credentiallibraries_v1_credential_library_proto protoreflect.FileDescriptor
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc = []byte{
@@ -309,99 +450,199 @@ var file_controller_api_resources_credentiallibraries_v1_credential_library_prot
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0xd9, 0x07, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x6f, 0x74, 0x6f, 0x22, 0xae, 0x0b, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x36, 0x0a, 0x13, 0x63, 0x72, 0x65,
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64,
0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xe3, 0x29, 0x01, 0x52, 0x13, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69,
0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70,
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62,
0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x4a, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x0f, 0xa0,
0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x48, 0x00,
0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xc1, 0x01, 0x0a,
0x23, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x73, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x51, 0x2e, 0x63, 0x6f, 0x6e,
0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x75,
0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72,
0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x1d, 0xa0,
0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x05, 0x76, 0x61, 0x75, 0x6c, 0x74, 0xfa, 0xd2, 0xe4, 0x93,
0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x20,
0x76, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c,
0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x12, 0x2f, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0xb6, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xda, 0x29,
0x01, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x12, 0x62, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x73, 0x18, 0xc0, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75,
0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x76, 0x65,
0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22,
0xee, 0x02, 0x0a, 0x20, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda,
0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x4e, 0x61,
0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29,
0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x5a,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x4a, 0x0a, 0x0a, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x0f, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29,
0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xc1, 0x01, 0x0a, 0x23, 0x76, 0x61, 0x75, 0x6c, 0x74,
0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72,
0x61, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x65,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x51, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72,
0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x1d, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29,
0x05, 0x76, 0x61, 0x75, 0x6c, 0x74, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e,
0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x20, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x43,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xfd, 0x01, 0x0a, 0x33, 0x76,
0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x5f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69,
0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74,
0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x2d, 0xa0, 0xda, 0x29, 0x01, 0x9a,
0xe3, 0x29, 0x15, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x73, 0x73, 0x68, 0x2d, 0x63, 0x65, 0x72,
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08,
0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x2e, 0x76, 0x61, 0x75, 0x6c,
0x74, 0x53, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xd8, 0x01, 0x0a, 0x2b, 0x76,
0x61, 0x75, 0x6c, 0x74, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x51, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x42, 0x25, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x0d, 0x76, 0x61, 0x75,
0x6c, 0x74, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a,
0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x27, 0x76, 0x61,
0x75, 0x6c, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03,
0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0xb6, 0x02, 0x20, 0x01, 0x28, 0x09,
0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x62, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x76,
0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x18, 0xc0, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x1c, 0x63,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e,
0x67, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x61,
0x74, 0x74, 0x72, 0x73, 0x22, 0xee, 0x02, 0x0a, 0x20, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74,
0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a,
0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68,
0x12, 0x09, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x12, 0x6c, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x83, 0x01, 0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2,
0xdd, 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64,
0x79, 0x12, 0x0f, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f,
0x64, 0x79, 0x52, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x5f, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xf1, 0x08, 0x0a, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x53,
0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x0f,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x12,
0x09, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68,
0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x12, 0x09, 0x56, 0x61, 0x75,
0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x6c, 0x0a, 0x0b,
0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42,
0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x12, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0b, 0x68,
0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x83, 0x01, 0x0a, 0x11, 0x68,
0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79,
0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x1c,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x0f, 0x48, 0x74,
0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x11, 0x68,
0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79,
0x42, 0x68, 0x5a, 0x66, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62,
0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x5f, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x13, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x12, 0x07, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6b, 0x65, 0x79,
0x54, 0x79, 0x70, 0x65, 0x12, 0x5f, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x74, 0x73,
0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x42, 0x26, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x13,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x62,
0x69, 0x74, 0x73, 0x12, 0x07, 0x4b, 0x65, 0x79, 0x42, 0x69, 0x74, 0x73, 0x52, 0x07, 0x6b, 0x65,
0x79, 0x42, 0x69, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x32, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x42, 0x1d, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x15, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x74, 0x6c, 0x12, 0x03, 0x54, 0x74, 0x6c, 0x52,
0x03, 0x74, 0x74, 0x6c, 0x12, 0x57, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x3c,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x11, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x12,
0x05, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0xd7, 0x01,
0x0a, 0x10, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0x46, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69,
0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74,
0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63,
0x61, 0x6c, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x36,
0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2e, 0x0a, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x0f, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0f, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xbc, 0x01, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65,
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x50, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x6f, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56,
0x61, 0x75, 0x6c, 0x74, 0x53, 0x53, 0x48, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72,
0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x45, 0x78,
0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x2b, 0xa0,
0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x23, 0x0a, 0x15, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x0a,
0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65,
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63,
0x61, 0x6c, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3d, 0x0a, 0x0f, 0x45, 0x78,
0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x68, 0x5a, 0x66, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70,
0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3b,
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72,
0x69, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -416,32 +657,46 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData
}
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_goTypes = []interface{}{
(*CredentialLibrary)(nil), // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary
(*VaultCredentialLibraryAttributes)(nil), // 1: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
(*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 5: google.protobuf.Struct
(*CredentialLibrary)(nil), // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary
(*VaultCredentialLibraryAttributes)(nil), // 1: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
(*VaultSSHCertificateCredentialLibraryAttributes)(nil), // 2: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes
nil, // 3: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
nil, // 4: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
(*scopes.ScopeInfo)(nil), // 5: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 6: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 8: google.protobuf.Struct
(*wrapperspb.UInt32Value)(nil), // 9: google.protobuf.UInt32Value
}
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs = []int32{
2, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
3, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
3, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
4, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
4, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
5, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
5, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
6, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
6, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
7, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
7, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
8, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
1, // 6: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
5, // 7: controller.api.resources.credentiallibraries.v1.CredentialLibrary.credential_mapping_overrides:type_name -> google.protobuf.Struct
3, // 8: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
3, // 9: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
3, // 10: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
2, // 7: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_ssh_certificate_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes
1, // 8: controller.api.resources.credentiallibraries.v1.CredentialLibrary.vault_generic_credential_library_attributes:type_name -> controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes
8, // 9: controller.api.resources.credentiallibraries.v1.CredentialLibrary.credential_mapping_overrides:type_name -> google.protobuf.Struct
6, // 10: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
6, // 11: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
6, // 12: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
6, // 13: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
6, // 14: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.username:type_name -> google.protobuf.StringValue
6, // 15: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_type:type_name -> google.protobuf.StringValue
9, // 16: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_bits:type_name -> google.protobuf.UInt32Value
6, // 17: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ttl:type_name -> google.protobuf.StringValue
6, // 18: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.key_id:type_name -> google.protobuf.StringValue
3, // 19: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.critical_options:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
4, // 20: controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.extensions:type_name -> controller.api.resources.credentiallibraries.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
21, // [21:21] is the sub-list for method output_type
21, // [21:21] is the sub-list for method input_type
21, // [21:21] is the sub-list for extension type_name
21, // [21:21] is the sub-list for extension extendee
0, // [0:21] is the sub-list for field type_name
}
func init() { file_controller_api_resources_credentiallibraries_v1_credential_library_proto_init() }
@@ -474,10 +729,24 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
return nil
}
}
file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VaultSSHCertificateCredentialLibraryAttributes); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[0].OneofWrappers = []interface{}{
(*CredentialLibrary_Attributes)(nil),
(*CredentialLibrary_VaultCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultGenericCredentialLibraryAttributes)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -485,7 +754,7 @@ func file_controller_api_resources_credentiallibraries_v1_credential_library_pro
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
@@ -256,7 +256,7 @@ func TestApiVaultCredentialStore(t *testing.T) {
// Create a credential library for the private key
clClient := credentiallibraries.NewClient(client)
newCredentialLibraryResult, err := clClient.Create(ctx, newCredentialStoreId,
newCredentialLibraryResult, err := clClient.Create(ctx, "vault", newCredentialStoreId,
credentiallibraries.WithVaultCredentialLibraryPath(c.VaultSecretPath+"/data/"+privateKeySecretName),
credentiallibraries.WithCredentialType("ssh_private_key"),
)
@@ -265,7 +265,7 @@ func TestApiVaultCredentialStore(t *testing.T) {
t.Logf("Created Credential Library: %s", newPrivateKeyCredentialLibraryId)
// Create a credential library for the password
newCredentialLibraryResult, err = clClient.Create(ctx, newCredentialStoreId,
newCredentialLibraryResult, err = clClient.Create(ctx, "vault", newCredentialStoreId,
credentiallibraries.WithVaultCredentialLibraryPath(c.VaultSecretPath+"/data/"+passwordSecretName),
credentiallibraries.WithCredentialType("username_password"),
)