get.porter.sh/porter@v1.3.0/pkg/storage/credential_store.go (about) 1 package storage 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "get.porter.sh/porter/pkg/secrets" 9 hostSecrets "get.porter.sh/porter/pkg/secrets/plugins/host" 10 "get.porter.sh/porter/pkg/tracing" 11 "github.com/cnabio/cnab-go/secrets/host" 12 "github.com/hashicorp/go-multierror" 13 ) 14 15 var _ CredentialSetProvider = &CredentialStore{} 16 17 const ( 18 CollectionCredentials = "credentials" 19 ) 20 21 // CredentialStore is a wrapper around Porter's datastore 22 // providing typed access and additional business logic around 23 // credential sets, usually referred to as "credentials" as a shorthand. 24 type CredentialStore struct { 25 Documents Store 26 Secrets secrets.Store 27 HostSecrets hostSecrets.Store 28 } 29 30 func NewCredentialStore(storage Store, secrets secrets.Store) *CredentialStore { 31 return &CredentialStore{ 32 Documents: storage, 33 Secrets: secrets, 34 HostSecrets: hostSecrets.NewStore(), 35 } 36 } 37 38 // EnsureCredentialIndices creates indices on the credentials collection. 39 func EnsureCredentialIndices(ctx context.Context, store Store) error { 40 ctx, span := tracing.StartSpan(ctx) 41 defer span.EndSpan() 42 43 span.Debug("Initializing credentials collection indices") 44 45 indices := EnsureIndexOptions{ 46 Indices: []Index{ 47 // query credentials by namespace + name 48 {Collection: CollectionCredentials, Keys: []string{"namespace", "name"}, Unique: true}, 49 }, 50 } 51 err := store.EnsureIndex(ctx, indices) 52 return span.Error(err) 53 } 54 55 func (s CredentialStore) GetDataStore() Store { 56 return s.Documents 57 } 58 59 /* 60 Secrets 61 */ 62 63 func (s CredentialStore) ResolveAll(ctx context.Context, creds CredentialSet, keys []string) (secrets.Set, error) { 64 ctx, span := tracing.StartSpan(ctx) 65 defer span.EndSpan() 66 67 resolvedCreds := make(secrets.Set) 68 var resolveErrors *multierror.Error 69 70 for _, key := range keys { 71 cred, ok := creds.GetCredential(key) 72 if !ok { 73 resolveErrors = multierror.Append(resolveErrors, span.Errorf("credential %s not found", key)) 74 continue 75 } 76 77 var value string 78 var err error 79 if isHandledByHostPlugin(cred.Source.Strategy) { 80 value, err = s.HostSecrets.Resolve(ctx, cred.Source.Strategy, cred.Source.Hint) 81 } else { 82 value, err = s.Secrets.Resolve(ctx, cred.Source.Strategy, cred.Source.Hint) 83 } 84 if err != nil { 85 resolveErrors = multierror.Append(resolveErrors, span.Errorf("unable to resolve credential %s.%s from %s %s: %w", creds.Name, cred.Name, cred.Source.Strategy, cred.Source.Hint, err)) 86 } 87 88 resolvedCreds[cred.Name] = value 89 } 90 91 return resolvedCreds, resolveErrors.ErrorOrNil() 92 } 93 94 func (s CredentialStore) Validate(ctx context.Context, creds CredentialSet) error { 95 validSources := []string{secrets.SourceSecret, host.SourceValue, host.SourceEnv, host.SourcePath, host.SourceCommand} 96 var errors error 97 98 for _, cs := range creds.Credentials { 99 valid := false 100 for _, validSource := range validSources { 101 if cs.Source.Strategy == validSource { 102 valid = true 103 break 104 } 105 } 106 if !valid { 107 errors = multierror.Append(errors, fmt.Errorf( 108 "%s is not a valid source. Valid sources are: %s", 109 cs.Source.Strategy, 110 strings.Join(validSources, ", "), 111 )) 112 } 113 } 114 115 return errors 116 } 117 118 /* 119 Document Storage 120 */ 121 122 func (s CredentialStore) InsertCredentialSet(ctx context.Context, creds CredentialSet) error { 123 creds.SchemaVersion = DefaultCredentialSetSchemaVersion 124 opts := InsertOptions{ 125 Documents: []interface{}{creds}, 126 } 127 return s.Documents.Insert(ctx, CollectionCredentials, opts) 128 } 129 130 func (s CredentialStore) ListCredentialSets(ctx context.Context, listOptions ListOptions) ([]CredentialSet, error) { 131 var out []CredentialSet 132 err := s.Documents.Find(ctx, CollectionCredentials, listOptions.ToFindOptions(), &out) 133 return out, err 134 } 135 136 func (s CredentialStore) GetCredentialSet(ctx context.Context, namespace string, name string) (CredentialSet, error) { 137 var out CredentialSet 138 opts := FindOptions{ 139 Filter: map[string]interface{}{ 140 "namespace": namespace, 141 "name": name, 142 }, 143 } 144 err := s.Documents.FindOne(ctx, CollectionCredentials, opts, &out) 145 return out, err 146 } 147 148 func (s CredentialStore) UpdateCredentialSet(ctx context.Context, creds CredentialSet) error { 149 creds.SchemaVersion = DefaultCredentialSetSchemaVersion 150 opts := UpdateOptions{ 151 Document: creds, 152 } 153 return s.Documents.Update(ctx, CollectionCredentials, opts) 154 } 155 156 func (s CredentialStore) UpsertCredentialSet(ctx context.Context, creds CredentialSet) error { 157 creds.SchemaVersion = DefaultCredentialSetSchemaVersion 158 opts := UpdateOptions{ 159 Document: creds, 160 Upsert: true, 161 } 162 return s.Documents.Update(ctx, CollectionCredentials, opts) 163 } 164 165 func (s CredentialStore) RemoveCredentialSet(ctx context.Context, namespace string, name string) error { 166 opts := RemoveOptions{ 167 Namespace: namespace, 168 Name: name, 169 } 170 return s.Documents.Remove(ctx, CollectionCredentials, opts) 171 }