mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Add gcp secrets
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
gcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin"
|
||||
kv "github.com/hashicorp/vault-plugin-secrets-kv"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
@@ -113,6 +114,7 @@ var (
|
||||
"cassandra": cassandra.Factory,
|
||||
"consul": consul.Factory,
|
||||
"database": database.Factory,
|
||||
"gcp": gcp.Factory,
|
||||
"kv": kv.Factory,
|
||||
"mongodb": mongodb.Factory,
|
||||
"mssql": mssql.Factory,
|
||||
|
||||
363
vendor/github.com/hashicorp/go-gcp-common/LICENSE
generated
vendored
Normal file
363
vendor/github.com/hashicorp/go-gcp-common/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
||||
111
vendor/github.com/hashicorp/go-gcp-common/gcputil/compute.go
generated
vendored
Normal file
111
vendor/github.com/hashicorp/go-gcp-common/gcputil/compute.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package gcputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ParseGcpLabels(labels []string) (parsed map[string]string, invalid []string) {
|
||||
parsed = map[string]string{}
|
||||
invalid = []string{}
|
||||
|
||||
re := regexp.MustCompile(labelRegex)
|
||||
for _, labelStr := range labels {
|
||||
matches := re.FindStringSubmatch(labelStr)
|
||||
if len(matches) == 0 {
|
||||
invalid = append(invalid, labelStr)
|
||||
continue
|
||||
}
|
||||
|
||||
captureNames := re.SubexpNames()
|
||||
var keyPtr, valPtr *string
|
||||
for i, name := range captureNames {
|
||||
if name == "key" {
|
||||
keyPtr = &matches[i]
|
||||
} else if name == "value" {
|
||||
valPtr = &matches[i]
|
||||
}
|
||||
}
|
||||
|
||||
if keyPtr == nil || valPtr == nil || len(*keyPtr) < 1 {
|
||||
invalid = append(invalid, labelStr)
|
||||
continue
|
||||
} else {
|
||||
parsed[*keyPtr] = *valPtr
|
||||
}
|
||||
}
|
||||
|
||||
return parsed, invalid
|
||||
}
|
||||
|
||||
type CustomJWTClaims struct {
|
||||
Google *GoogleJWTClaims `json:"google,omitempty"`
|
||||
}
|
||||
|
||||
type GoogleJWTClaims struct {
|
||||
Compute *GCEIdentityMetadata `json:"compute_engine,omitempty"`
|
||||
}
|
||||
|
||||
type GCEIdentityMetadata struct {
|
||||
// ProjectId is the ID for the project where you created the instance.
|
||||
ProjectId string `json:"project_id" structs:"project_id" mapstructure:"project_id"`
|
||||
|
||||
// ProjectNumber is the unique ID for the project where you created the instance.
|
||||
ProjectNumber int64 `json:"project_number" structs:"project_number" mapstructure:"project_number"`
|
||||
|
||||
// Zone is the zone where the instance is located.
|
||||
Zone string `json:"zone" structs:"zone" mapstructure:"zone"`
|
||||
|
||||
// InstanceId is the unique ID for the instance to which this token belongs. This ID is unique and never reused.
|
||||
InstanceId string `json:"instance_id" structs:"instance_id" mapstructure:"instance_id"`
|
||||
|
||||
// InstanceName is the name of the instance to which this token belongs. This name can be reused by several
|
||||
// instances over time, so use the instance_id value to identify a unique instance ID.
|
||||
InstanceName string `json:"instance_name" structs:"instance_name" mapstructure:"instance_name"`
|
||||
|
||||
// CreatedAt is a unix timestamp indicating when you created the instance.
|
||||
CreatedAt int64 `json:"instance_creation_timestamp" structs:"instance_creation_timestamp" mapstructure:"instance_creation_timestamp"`
|
||||
}
|
||||
|
||||
// GetVerifiedInstance returns the Instance as described by the identity metadata or an error.
|
||||
// If the instance has an invalid status or its creation timestamp does not match the metadata value,
|
||||
// this will return nil and an error.
|
||||
func (meta *GCEIdentityMetadata) GetVerifiedInstance(gceClient *compute.Service) (*compute.Instance, error) {
|
||||
instance, err := gceClient.Instances.Get(meta.ProjectId, meta.Zone, meta.InstanceName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find instance associated with token: %v", err)
|
||||
}
|
||||
|
||||
if !IsValidInstanceStatus(instance.Status) {
|
||||
return nil, fmt.Errorf("authenticating instance %s found but has invalid status '%s'", instance.Name, instance.Status)
|
||||
}
|
||||
|
||||
// Parse the metadata CreatedAt into time.
|
||||
metaTime := time.Unix(meta.CreatedAt, 0)
|
||||
|
||||
// Parse instance creationTimestamp into time.
|
||||
actualTime, err := time.Parse(time.RFC3339Nano, instance.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instance 'creationTimestamp' field could not be parsed into time: %s", instance.CreationTimestamp)
|
||||
}
|
||||
|
||||
// Return an error if the metadata creation timestamp is before the instance creation timestamp.
|
||||
delta := float64(metaTime.Sub(actualTime)) / float64(time.Second)
|
||||
if delta < -1 {
|
||||
return nil, fmt.Errorf("metadata instance_creation_timestamp %d is before instance's creation time %d", actualTime.Unix(), metaTime.Unix())
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
var validInstanceStates map[string]struct{} = map[string]struct{}{
|
||||
"PROVISIONING": struct{}{},
|
||||
"RUNNING": struct{}{},
|
||||
"STAGING": struct{}{},
|
||||
}
|
||||
|
||||
func IsValidInstanceStatus(status string) bool {
|
||||
_, ok := validInstanceStates[status]
|
||||
return ok
|
||||
}
|
||||
178
vendor/github.com/hashicorp/go-gcp-common/gcputil/credentials.go
generated
vendored
Normal file
178
vendor/github.com/hashicorp/go-gcp-common/gcputil/credentials.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package gcputil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
googleoauth2 "google.golang.org/api/oauth2/v2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
labelRegex string = "^(?P<key>[a-z]([\\w-]+)?):(?P<value>[\\w-]*)$"
|
||||
defaultHomeCredentialsFile = ".gcp/credentials"
|
||||
)
|
||||
|
||||
// GcpCredentials represents a simplified version of the Google Cloud Platform credentials file format.
|
||||
type GcpCredentials struct {
|
||||
ClientEmail string `json:"client_email" structs:"client_email" mapstructure:"client_email"`
|
||||
ClientId string `json:"client_id" structs:"client_id" mapstructure:"client_id"`
|
||||
PrivateKeyId string `json:"private_key_id" structs:"private_key_id" mapstructure:"private_key_id"`
|
||||
PrivateKey string `json:"private_key" structs:"private_key" mapstructure:"private_key"`
|
||||
ProjectId string `json:"project_id" structs:"project_id" mapstructure:"project_id"`
|
||||
}
|
||||
|
||||
// FindCredentials attempts to obtain GCP credentials in the
|
||||
// following ways:
|
||||
// * Parse JSON from provided credentialsJson
|
||||
// * Parse JSON from the environment variables GOOGLE_CREDENTIALS or GOOGLE_CLOUD_KEYFILE_JSON
|
||||
// * Parse JSON file ~/.gcp/credentials
|
||||
// * Google Application Default Credentials (see https://developers.google.com/identity/protocols/application-default-credentials)
|
||||
func FindCredentials(credsJson string, ctx context.Context, scopes ...string) (*GcpCredentials, oauth2.TokenSource, error) {
|
||||
var creds *GcpCredentials
|
||||
var err error
|
||||
// 1. Parse JSON from provided credentialsJson
|
||||
if credsJson == "" {
|
||||
// 2. JSON from env var GOOGLE_CREDENTIALS
|
||||
credsJson = os.Getenv("GOOGLE_CREDENTIALS")
|
||||
}
|
||||
|
||||
if credsJson == "" {
|
||||
// 3. JSON from env var GOOGLE_CLOUD_KEYFILE_JSON
|
||||
credsJson = os.Getenv("GOOGLE_CLOUD_KEYFILE_JSON")
|
||||
}
|
||||
|
||||
if credsJson == "" {
|
||||
// 4. JSON from ~/.gcp/credentials
|
||||
home, err := homedir.Dir()
|
||||
if err == nil {
|
||||
return nil, nil, errors.New("could not find home directory")
|
||||
}
|
||||
credBytes, err := ioutil.ReadFile(filepath.Join(home, defaultHomeCredentialsFile))
|
||||
if err == nil {
|
||||
credsJson = string(credBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON into credentials.
|
||||
if credsJson != "" {
|
||||
creds, err = Credentials(credsJson)
|
||||
if err == nil {
|
||||
conf := jwt.Config{
|
||||
Email: creds.ClientEmail,
|
||||
PrivateKey: []byte(creds.PrivateKey),
|
||||
Scopes: scopes,
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
}
|
||||
return creds, conf.TokenSource(ctx), nil
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Use Application default credentials.
|
||||
defaultCreds, err := google.FindDefaultCredentials(ctx, scopes...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if defaultCreds.JSON != nil {
|
||||
creds, err = Credentials(string(defaultCreds.JSON))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("could not read credentials from application default credential JSON")
|
||||
}
|
||||
}
|
||||
|
||||
return creds, defaultCreds.TokenSource, nil
|
||||
}
|
||||
|
||||
// Credentials attempts to parse GcpCredentials from a JSON string.
|
||||
func Credentials(credentialsJson string) (*GcpCredentials, error) {
|
||||
credentials := &GcpCredentials{}
|
||||
if err := json.Unmarshal([]byte(credentialsJson), &credentials); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// GetHttpClient creates an HTTP client from the given Google credentials and scopes.
|
||||
func GetHttpClient(credentials *GcpCredentials, clientScopes ...string) (*http.Client, error) {
|
||||
conf := jwt.Config{
|
||||
Email: credentials.ClientEmail,
|
||||
PrivateKey: []byte(credentials.PrivateKey),
|
||||
Scopes: clientScopes,
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cleanhttp.DefaultClient())
|
||||
client := conf.Client(ctx)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// PublicKey returns a public key from a Google PEM key file (type TYPE_X509_PEM_FILE).
|
||||
func PublicKey(pemString string) (interface{}, error) {
|
||||
pemBytes, err := base64.StdEncoding.DecodeString(pemString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("Unable to find pem block in key")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert.PublicKey, nil
|
||||
}
|
||||
|
||||
// OAuth2RSAPublicKey returns the PEM key file string for Google Oauth2 public cert for the given 'kid' id.
|
||||
func OAuth2RSAPublicKey(kid, oauth2BasePath string) (interface{}, error) {
|
||||
oauth2Client, err := googleoauth2.New(cleanhttp.DefaultClient())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(oauth2BasePath) > 0 {
|
||||
oauth2Client.BasePath = oauth2BasePath
|
||||
}
|
||||
|
||||
jwks, err := oauth2Client.GetCertForOpenIdConnect().Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, key := range jwks.Keys {
|
||||
if key.Kid == kid && jose.SignatureAlgorithm(key.Alg) == jose.RS256 {
|
||||
// Trim extra '=' from key so it can be parsed.
|
||||
key.N = strings.TrimRight(key.N, "=")
|
||||
js, err := key.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal json %v", err)
|
||||
}
|
||||
key := &jose.JSONWebKey{}
|
||||
if err := key.UnmarshalJSON(js); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal json %v", err)
|
||||
}
|
||||
|
||||
return key.Key, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find public key with kid '%s'", kid)
|
||||
}
|
||||
51
vendor/github.com/hashicorp/go-gcp-common/gcputil/iam_admin.go
generated
vendored
Normal file
51
vendor/github.com/hashicorp/go-gcp-common/gcputil/iam_admin.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package gcputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"google.golang.org/api/iam/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceAccountTemplate = "projects/%s/serviceAccounts/%s"
|
||||
ServiceAccountKeyTemplate = "projects/%s/serviceAccounts/%s/keys/%s"
|
||||
ServiceAccountKeyFileType = "TYPE_X509_PEM_FILE"
|
||||
)
|
||||
|
||||
type ServiceAccountId struct {
|
||||
Project string
|
||||
EmailOrId string
|
||||
}
|
||||
|
||||
func (id *ServiceAccountId) ResourceName() string {
|
||||
return fmt.Sprintf(ServiceAccountTemplate, id.Project, id.EmailOrId)
|
||||
}
|
||||
|
||||
type ServiceAccountKeyId struct {
|
||||
Project string
|
||||
EmailOrId string
|
||||
Key string
|
||||
}
|
||||
|
||||
func (id *ServiceAccountKeyId) ResourceName() string {
|
||||
return fmt.Sprintf(ServiceAccountKeyTemplate, id.Project, id.EmailOrId, id.Key)
|
||||
}
|
||||
|
||||
// ServiceAccount wraps a call to the GCP IAM API to get a service account.
|
||||
func ServiceAccount(iamClient *iam.Service, accountId *ServiceAccountId) (*iam.ServiceAccount, error) {
|
||||
account, err := iamClient.Projects.ServiceAccounts.Get(accountId.ResourceName()).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find service account '%s': %v", accountId.ResourceName(), err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// ServiceAccountKey wraps a call to the GCP IAM API to get a service account key.
|
||||
func ServiceAccountKey(iamClient *iam.Service, keyId *ServiceAccountKeyId) (*iam.ServiceAccountKey, error) {
|
||||
keyResource := keyId.ResourceName()
|
||||
key, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyId.ResourceName()).PublicKeyType(ServiceAccountKeyFileType).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find service account key '%s': %v", keyResource, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
124
vendor/github.com/hashicorp/go-gcp-common/gcputil/resource_name.go
generated
vendored
Normal file
124
vendor/github.com/hashicorp/go-gcp-common/gcputil/resource_name.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package gcputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceIdRegex = "^[^\t\n\f\r]+$"
|
||||
collectionIdRegex = "^[a-z][a-zA-Z]*$"
|
||||
|
||||
fullResourceNameRegex = "^//([a-z]+).googleapis.com/(.+)$"
|
||||
selfLinkMarker = "projects/"
|
||||
)
|
||||
|
||||
var singleCollectionIds = map[string]struct{}{
|
||||
"global": {},
|
||||
}
|
||||
|
||||
type RelativeResourceName struct {
|
||||
Name string
|
||||
TypeKey string
|
||||
IdTuples map[string]string
|
||||
}
|
||||
|
||||
func ParseRelativeName(resource string) (*RelativeResourceName, error) {
|
||||
resourceRe := regexp.MustCompile(resourceIdRegex)
|
||||
collectionRe := regexp.MustCompile(collectionIdRegex)
|
||||
|
||||
tokens := strings.Split(resource, "/")
|
||||
if len(tokens) < 2 {
|
||||
return nil, fmt.Errorf("invalid relative resource name %s (too few tokens)", resource)
|
||||
}
|
||||
|
||||
ids := map[string]string{}
|
||||
typeKey := ""
|
||||
currColId := ""
|
||||
for idx, v := range tokens {
|
||||
if len(currColId) == 0 {
|
||||
if _, ok := singleCollectionIds[v]; ok {
|
||||
// Ignore 'single' collectionIds like Global, but error if they are the last ID
|
||||
if idx == len(tokens)-1 {
|
||||
return nil, fmt.Errorf("invalid relative resource name %s (last collection '%s' has no ID)", resource, currColId)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(collectionRe.FindAllString(v, 1)) == 0 {
|
||||
return nil, fmt.Errorf("invalid relative resource name %s (invalid collection ID %s)", resource, v)
|
||||
}
|
||||
currColId = v
|
||||
typeKey += currColId + "/"
|
||||
} else {
|
||||
if len(resourceRe.FindAllString(v, 1)) == 0 {
|
||||
return nil, fmt.Errorf("invalid relative resource name %s (invalid resource sub-ID %s)", resource, v)
|
||||
}
|
||||
ids[currColId] = v
|
||||
currColId = ""
|
||||
}
|
||||
}
|
||||
|
||||
resourceName := tokens[len(tokens)-2]
|
||||
return &RelativeResourceName{
|
||||
Name: resourceName,
|
||||
TypeKey: typeKey[:len(typeKey)-1],
|
||||
IdTuples: ids,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FullResourceName struct {
|
||||
Service string
|
||||
*RelativeResourceName
|
||||
}
|
||||
|
||||
func ParseFullResourceName(name string) (*FullResourceName, error) {
|
||||
fullRe := regexp.MustCompile(fullResourceNameRegex)
|
||||
matches := fullRe.FindAllStringSubmatch(name, 1)
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("invalid full name '%s'", name)
|
||||
}
|
||||
|
||||
if len(matches[0]) != 3 {
|
||||
return nil, fmt.Errorf("invalid full name '%s'", name)
|
||||
}
|
||||
|
||||
serviceName := matches[0][1]
|
||||
relName, err := ParseRelativeName(strings.Trim(matches[0][2], "/"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing relative resource path in full resource name '%s': %v", name, err)
|
||||
}
|
||||
|
||||
return &FullResourceName{
|
||||
Service: serviceName,
|
||||
RelativeResourceName: relName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SelfLink struct {
|
||||
Prefix string
|
||||
*RelativeResourceName
|
||||
}
|
||||
|
||||
func ParseProjectResourceSelfLink(link string) (*SelfLink, error) {
|
||||
u, err := url.Parse(link)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return nil, fmt.Errorf("invalid self link '%s' must have scheme/host", link)
|
||||
}
|
||||
|
||||
split := strings.SplitAfterN(link, selfLinkMarker, 2)
|
||||
if len(split) != 2 {
|
||||
return nil, fmt.Errorf("self link '%s' is not for project-level resource, must contain '%s')", link, selfLinkMarker)
|
||||
}
|
||||
|
||||
relName, err := ParseRelativeName(selfLinkMarker + split[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing relative resource path in self-link '%s': %v", link, err)
|
||||
}
|
||||
|
||||
return &SelfLink{
|
||||
Prefix: strings.TrimSuffix(split[0], selfLinkMarker),
|
||||
RelativeResourceName: relName,
|
||||
}, nil
|
||||
}
|
||||
363
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/LICENSE
generated
vendored
Normal file
363
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
||||
114
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/backend.go
generated
vendored
Normal file
114
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/backend.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
*framework.Backend
|
||||
|
||||
enabledIamResources iamutil.EnabledResources
|
||||
|
||||
rolesetLock sync.Mutex
|
||||
}
|
||||
|
||||
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := Backend()
|
||||
if err := b.Setup(ctx, conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func Backend() *backend {
|
||||
var b = backend{
|
||||
enabledIamResources: iamutil.GetEnabledIamResources(),
|
||||
}
|
||||
|
||||
b.Backend = &framework.Backend{
|
||||
Help: strings.TrimSpace(backendHelp),
|
||||
PathsSpecial: &logical.Paths{
|
||||
LocalStorage: []string{
|
||||
framework.WALPrefix,
|
||||
},
|
||||
SealWrapStorage: []string{
|
||||
"config",
|
||||
},
|
||||
},
|
||||
|
||||
Paths: framework.PathAppend(
|
||||
pathsRoleSet(&b),
|
||||
[]*framework.Path{
|
||||
pathConfig(&b),
|
||||
pathSecretAccessToken(&b),
|
||||
pathSecretServiceAccountKey(&b),
|
||||
},
|
||||
),
|
||||
Secrets: []*framework.Secret{
|
||||
secretAccessToken(&b),
|
||||
secretServiceAccountKey(&b),
|
||||
},
|
||||
|
||||
BackendType: logical.TypeLogical,
|
||||
WALRollback: b.walRollback,
|
||||
WALRollbackMinAge: 5 * time.Minute,
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
func newHttpClient(ctx context.Context, s logical.Storage, scopes ...string) (*http.Client, error) {
|
||||
if len(scopes) == 0 {
|
||||
scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
||||
}
|
||||
|
||||
cfg, err := getConfig(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credsJSON := ""
|
||||
if cfg != nil {
|
||||
credsJSON = cfg.CredentialsRaw
|
||||
}
|
||||
|
||||
_, tokenSource, err := gcputil.FindCredentials(credsJSON, ctx, scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc := cleanhttp.DefaultClient()
|
||||
return oauth2.NewClient(
|
||||
context.WithValue(ctx, oauth2.HTTPClient, tc),
|
||||
tokenSource), nil
|
||||
}
|
||||
|
||||
func newIamAdmin(ctx context.Context, s logical.Storage) (*iam.Service, error) {
|
||||
c, err := newHttpClient(ctx, s, iam.CloudPlatformScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return iam.New(c)
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The GCP secrets backend dynamically generates GCP IAM service
|
||||
account keys with a given set of IAM policies. The service
|
||||
account keys have a configurable lease set and are automatically
|
||||
revoked at the end of the lease.
|
||||
|
||||
After mounting this backend, credentials to generate IAM keys must
|
||||
be configured with the "config/" endpoints and policies must be
|
||||
written using the "roles/" endpoints before any keys can be generated.
|
||||
`
|
||||
77
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_handle.go
generated
vendored
Normal file
77
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_handle.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package iamutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"google.golang.org/api/gensupport"
|
||||
"google.golang.org/api/googleapi"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type IamHandle struct {
|
||||
c *http.Client
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func GetIamHandle(client *http.Client, userAgent string) *IamHandle {
|
||||
return &IamHandle{
|
||||
c: client,
|
||||
userAgent: userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *IamHandle) GetIamPolicy(ctx context.Context, r IamResource) (*Policy, error) {
|
||||
req, err := r.GetIamPolicyRequest()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to construct GetIamPolicy request: {{err}}", err)
|
||||
}
|
||||
var p Policy
|
||||
if err := h.doRequest(ctx, req, &p); err != nil {
|
||||
return nil, errwrap.Wrapf("unable to get policy: {{err}}", err)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (h *IamHandle) SetIamPolicy(ctx context.Context, r IamResource, p *Policy) (*Policy, error) {
|
||||
req, err := r.SetIamPolicyRequest(p)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to construct SetIamPolicy request: {{err}}", err)
|
||||
}
|
||||
var out Policy
|
||||
if err := h.doRequest(ctx, req, &out); err != nil {
|
||||
return nil, errwrap.Wrapf("unable to set policy: {{err}}", err)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (h *IamHandle) doRequest(ctx context.Context, req *http.Request, out interface{}) error {
|
||||
if req.Header == nil {
|
||||
req.Header = make(http.Header)
|
||||
}
|
||||
if h.userAgent != "" {
|
||||
req.Header.Set("User-Agent", h.userAgent)
|
||||
}
|
||||
|
||||
resp, err := gensupport.SendRequest(ctx, h.c, req)
|
||||
defer googleapi.CloseBody(resp)
|
||||
|
||||
if resp != nil && resp.StatusCode == http.StatusNotModified {
|
||||
return &googleapi.Error{
|
||||
Code: resp.StatusCode,
|
||||
Header: resp.Header,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := googleapi.CheckResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return errwrap.Wrapf("unable to decode JSON resp to output interface: {{err}}", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
100
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_policy.go
generated
vendored
Normal file
100
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_policy.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package iamutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceAccountMemberTmpl = "serviceAccount:%s"
|
||||
)
|
||||
|
||||
type Policy struct {
|
||||
Bindings []*Binding `json:"bindings,omitempty"`
|
||||
Etag string `json:"etag,omitempty"`
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
Members []string `json:"members,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
type PolicyDelta struct {
|
||||
Roles util.StringSet
|
||||
Email string
|
||||
}
|
||||
|
||||
func (p *Policy) AddBindings(toAdd *PolicyDelta) (changed bool, updated *Policy) {
|
||||
return p.ChangedBindings(toAdd, nil)
|
||||
}
|
||||
|
||||
func (p *Policy) RemoveBindings(toRemove *PolicyDelta) (changed bool, updated *Policy) {
|
||||
return p.ChangedBindings(nil, toRemove)
|
||||
}
|
||||
|
||||
func (p *Policy) ChangedBindings(toAdd *PolicyDelta, toRemove *PolicyDelta) (changed bool, updated *Policy) {
|
||||
if toAdd == nil && toRemove == nil {
|
||||
return false, p
|
||||
}
|
||||
|
||||
var toAddMem, toRemoveMem string
|
||||
if toAdd != nil {
|
||||
toAddMem = fmt.Sprintf(ServiceAccountMemberTmpl, toAdd.Email)
|
||||
}
|
||||
if toRemove != nil {
|
||||
toRemoveMem = fmt.Sprintf(ServiceAccountMemberTmpl, toRemove.Email)
|
||||
}
|
||||
|
||||
changed = false
|
||||
|
||||
newBindings := make([]*Binding, 0, len(p.Bindings))
|
||||
alreadyAdded := make(util.StringSet)
|
||||
|
||||
for _, bind := range p.Bindings {
|
||||
memberSet := util.ToSet(bind.Members)
|
||||
|
||||
if toAdd != nil {
|
||||
if toAdd.Roles.Includes(bind.Role) {
|
||||
changed = true
|
||||
alreadyAdded.Add(bind.Role)
|
||||
memberSet.Add(toAddMem)
|
||||
}
|
||||
}
|
||||
|
||||
if toRemove != nil {
|
||||
if toRemove.Roles.Includes(bind.Role) {
|
||||
if memberSet.Includes(toRemoveMem) {
|
||||
changed = true
|
||||
delete(memberSet, toRemoveMem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(memberSet) > 0 {
|
||||
newBindings = append(newBindings, &Binding{
|
||||
Role: bind.Role,
|
||||
Members: memberSet.ToSlice(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if toAdd != nil {
|
||||
for r := range toAdd.Roles {
|
||||
if !alreadyAdded.Includes(r) {
|
||||
changed = true
|
||||
newBindings = append(newBindings, &Binding{
|
||||
Role: r,
|
||||
Members: []string{toAddMem},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
return true, &Policy{
|
||||
Bindings: newBindings,
|
||||
Etag: p.Etag,
|
||||
}
|
||||
}
|
||||
return false, p
|
||||
}
|
||||
253
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources.go
generated
vendored
Normal file
253
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources.go
generated
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
//go:generate go run internal/generate_iam_resources.go
|
||||
|
||||
package iamutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"google.golang.org/api/googleapi"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceParsingErrorTmpl = `invalid resource "%s": %v`
|
||||
resourceMultipleServicesTmpl = `please provide a self-link or full resource name for non-service-unique resource type '%s' (supported services: %s)`
|
||||
resourceMultipleVersions = `please provide a self-link with version instead; IAM support for this resource is for multiple non-preferred service versions`
|
||||
)
|
||||
|
||||
type EnabledResources interface {
|
||||
Resource(resource string) (IamResource, error)
|
||||
}
|
||||
|
||||
type iamResourceMap map[string]map[string]map[string]*IamResourceConfig
|
||||
|
||||
type generatedIamResources struct {
|
||||
resources iamResourceMap
|
||||
}
|
||||
|
||||
func (apis *generatedIamResources) parseResource(name string) (*gcputil.RelativeResourceName, *IamResourceConfig, error) {
|
||||
rUrl, err := url.Parse(name)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(`resource "%s" is invalid URI`, name)
|
||||
}
|
||||
|
||||
var relName *gcputil.RelativeResourceName
|
||||
var hasServiceVersion bool
|
||||
var serviceName string
|
||||
|
||||
if rUrl.Scheme != "" {
|
||||
selfLink, err := gcputil.ParseProjectResourceSelfLink(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hasServiceVersion = true
|
||||
relName = selfLink.RelativeResourceName
|
||||
} else if rUrl.Host != "" {
|
||||
fullName, err := gcputil.ParseFullResourceName(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
relName = fullName.RelativeResourceName
|
||||
serviceName = fullName.Service
|
||||
} else {
|
||||
relName, err = gcputil.ParseRelativeName(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if relName == nil {
|
||||
return nil, nil, fmt.Errorf(resourceParsingErrorTmpl, name, "unable to parse relative name")
|
||||
}
|
||||
|
||||
serviceMap, ok := apis.resources[relName.TypeKey]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf(resourceParsingErrorTmpl, name, fmt.Errorf("unsupported resource type: %s", relName.TypeKey))
|
||||
}
|
||||
|
||||
var resConfig *IamResourceConfig
|
||||
if hasServiceVersion {
|
||||
resConfig, err = tryGetConfigForSelfLink(name, relName.TypeKey, serviceMap)
|
||||
} else {
|
||||
resConfig, err = tryGetUniqueVersion(serviceName, relName.TypeKey, serviceMap)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resConfig == nil {
|
||||
return nil, nil, fmt.Errorf(resourceParsingErrorTmpl, name, "unable to get IAM resource config")
|
||||
}
|
||||
|
||||
return relName, resConfig, nil
|
||||
}
|
||||
|
||||
type IamResource interface {
|
||||
GetIamPolicyRequest() (*http.Request, error)
|
||||
SetIamPolicyRequest(*Policy) (*http.Request, error)
|
||||
}
|
||||
|
||||
func (apis *generatedIamResources) Resource(name string) (IamResource, error) {
|
||||
relName, cfg, err := apis.parseResource(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iamResourceImpl{
|
||||
relativeId: relName,
|
||||
config: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func tryGetConfigForSelfLink(link, typeKey string, resourceServices map[string]map[string]*IamResourceConfig) (*IamResourceConfig, error) {
|
||||
for _, verMap := range resourceServices {
|
||||
for _, resourceCfg := range verMap {
|
||||
prefix := resourceCfg.Service.RootUrl + resourceCfg.Service.ServicePath
|
||||
if strings.HasPrefix(link, prefix) {
|
||||
return resourceCfg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find service/version given in self-link for resource type %s", typeKey)
|
||||
}
|
||||
|
||||
func tryGetUniqueVersion(serviceName, typeKey string, resourceServices map[string]map[string]*IamResourceConfig) (*IamResourceConfig, error) {
|
||||
if serviceName == "" {
|
||||
return tryGetUniqueServiceAndVersion(typeKey, resourceServices)
|
||||
}
|
||||
if resourceServices == nil {
|
||||
return nil, fmt.Errorf("no supported services for %s", typeKey)
|
||||
}
|
||||
verMap, hasService := resourceServices[serviceName]
|
||||
if !hasService {
|
||||
return nil, fmt.Errorf("unsupported service '%s' for resource type: %s", serviceName, typeKey)
|
||||
}
|
||||
return getResourceFromVersions(verMap)
|
||||
}
|
||||
|
||||
func tryGetUniqueServiceAndVersion(typeKey string, resourceServices map[string]map[string]*IamResourceConfig) (*IamResourceConfig, error) {
|
||||
if resourceServices == nil || len(resourceServices) < 1 {
|
||||
return nil, fmt.Errorf("no supported services for %s", typeKey)
|
||||
}
|
||||
isUnique := len(resourceServices) == 1
|
||||
supported := ""
|
||||
for serviceName, verMap := range resourceServices {
|
||||
supported += serviceName + ", "
|
||||
if isUnique {
|
||||
return getResourceFromVersions(verMap)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(resourceMultipleServicesTmpl, typeKey, strings.Trim(supported, ", "))
|
||||
}
|
||||
|
||||
func getResourceFromVersions(versionsMap map[string]*IamResourceConfig) (*IamResourceConfig, error) {
|
||||
var preferredVer *IamResourceConfig
|
||||
var onlyCfg *IamResourceConfig
|
||||
|
||||
for _, onlyCfg = range versionsMap {
|
||||
if onlyCfg.Service.IsPreferredVersion {
|
||||
preferredVer = onlyCfg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if preferredVer != nil {
|
||||
return preferredVer, nil
|
||||
} else if len(versionsMap) == 1 {
|
||||
return onlyCfg, nil
|
||||
} else {
|
||||
return nil, errors.New(resourceMultipleVersions)
|
||||
}
|
||||
}
|
||||
|
||||
type iamResourceImpl struct {
|
||||
relativeId *gcputil.RelativeResourceName
|
||||
config *IamResourceConfig
|
||||
}
|
||||
|
||||
type IamResourceConfig struct {
|
||||
// Service this resource belongs to
|
||||
Service *ServiceConfig
|
||||
|
||||
// Config for IAM Methods
|
||||
SetIamPolicy *HttpMethodCfg
|
||||
GetIamPolicy *HttpMethodCfg
|
||||
}
|
||||
|
||||
type HttpMethodCfg struct {
|
||||
// HTTP method, e.g. GET/PUT/POST
|
||||
HttpMethod string `json:"httpMethod"`
|
||||
|
||||
// Path is the API method's path with replacement keys, e.g.
|
||||
// v1/projects/{project}:getIamPolicy
|
||||
Path string `json:"flatPath"`
|
||||
|
||||
// ReplacementKeys maps collectionIds in the expected resource format to the key for googleapis.Expand
|
||||
// For example, given input of:
|
||||
// Resource: "projects/my-project/zones/my-zone/instances/someInstance"
|
||||
// Method Path: "p/{projectId}/z/{zoneId}/i/{resource}"
|
||||
//
|
||||
// This would be:
|
||||
// map[string]string{
|
||||
// "projects": "projectId" ,
|
||||
// "zones": "zoneId",
|
||||
// "instances": "resource"
|
||||
// }
|
||||
ReplacementKeys map[string]string
|
||||
}
|
||||
|
||||
type ServiceConfig struct {
|
||||
// API service Name (e.g. "compute", "iam", "pubsub")
|
||||
Name string
|
||||
|
||||
// API service Version.
|
||||
Version string
|
||||
|
||||
// IsPreferredVersion is
|
||||
IsPreferredVersion bool
|
||||
|
||||
// Root URL + Service Path is the prefix for all calls using this service.
|
||||
RootUrl string
|
||||
ServicePath string
|
||||
}
|
||||
|
||||
func (r *iamResourceImpl) SetIamPolicyRequest(p *Policy) (*http.Request, error) {
|
||||
data := struct {
|
||||
Policy *Policy `json:"policy,omitempty"`
|
||||
}{Policy: p}
|
||||
|
||||
buf, err := googleapi.WithoutDataWrapper.JSONReader(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.constructRequest(r.config.SetIamPolicy, buf)
|
||||
}
|
||||
|
||||
func (r *iamResourceImpl) GetIamPolicyRequest() (*http.Request, error) {
|
||||
return r.constructRequest(r.config.GetIamPolicy, nil)
|
||||
}
|
||||
|
||||
func (r *iamResourceImpl) constructRequest(httpMtd *HttpMethodCfg, data io.Reader) (*http.Request, error) {
|
||||
reqUrl := googleapi.ResolveRelative(r.config.Service.RootUrl+r.config.Service.ServicePath, httpMtd.Path)
|
||||
req, err := http.NewRequest(httpMtd.HttpMethod, reqUrl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
replacementMap := make(map[string]string)
|
||||
for cId, replaceK := range httpMtd.ReplacementKeys {
|
||||
rId, ok := r.relativeId.IdTuples[cId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected value for collection id %s", cId)
|
||||
}
|
||||
replacementMap[replaceK] = rId
|
||||
}
|
||||
|
||||
googleapi.Expand(req.URL, replacementMap)
|
||||
return req, nil
|
||||
}
|
||||
1365
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources_generated.go
generated
vendored
Normal file
1365
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil/iam_resources_generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
136
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/path_config.go
generated
vendored
Normal file
136
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/path_config.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
cfgReadWarning = "omitted sensitive credentials from read output"
|
||||
)
|
||||
|
||||
func pathConfig(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "config",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"credentials": {
|
||||
Type: framework.TypeString,
|
||||
Description: `GCP IAM service account credentials JSON with permissions to create new service accounts and set IAM policies`,
|
||||
},
|
||||
"ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "Default lease for generated keys. If <= 0, will use system default.",
|
||||
},
|
||||
"max_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "Maximum time a service account key is valid for. If <= 0, will use system default.",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathConfigRead,
|
||||
logical.UpdateOperation: b.pathConfigWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathConfigHelpSyn,
|
||||
HelpDescription: pathConfigHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
cfg, err := getConfig(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"ttl": int64(cfg.TTL / time.Second),
|
||||
"max_ttl": int64(cfg.MaxTTL / time.Second),
|
||||
},
|
||||
Warnings: []string{cfgReadWarning},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
cfg, err := getConfig(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
cfg = &config{}
|
||||
}
|
||||
|
||||
credentialsRaw, ok := data.GetOk("credentials")
|
||||
if ok {
|
||||
_, err := gcputil.Credentials(credentialsRaw.(string))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid credentials JSON file: %v", err)), nil
|
||||
}
|
||||
cfg.CredentialsRaw = credentialsRaw.(string)
|
||||
}
|
||||
|
||||
// Update token TTL.
|
||||
ttlRaw, ok := data.GetOk("ttl")
|
||||
if ok {
|
||||
cfg.TTL = time.Duration(ttlRaw.(int)) * time.Second
|
||||
}
|
||||
|
||||
// Update token Max TTL.
|
||||
maxTTLRaw, ok := data.GetOk("max_ttl")
|
||||
if ok {
|
||||
cfg.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type config struct {
|
||||
CredentialsRaw string
|
||||
|
||||
TTL time.Duration
|
||||
MaxTTL time.Duration
|
||||
}
|
||||
|
||||
func getConfig(ctx context.Context, s logical.Storage) (*config, error) {
|
||||
var cfg config
|
||||
cfgRaw, err := s.Get(ctx, "config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfgRaw == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := cfgRaw.DecodeJSON(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
const pathConfigHelpSyn = `
|
||||
Configure the GCP backend.
|
||||
`
|
||||
|
||||
const pathConfigHelpDesc = `
|
||||
The GCP backend requires credentials for managing IAM service accounts and keys
|
||||
and IAM policies on various GCP resources. This endpoint is used to configure
|
||||
those credentials as well as default values for the backend in general.
|
||||
`
|
||||
520
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/path_role_set.go
generated
vendored
Normal file
520
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/path_role_set.go
generated
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util"
|
||||
"github.com/hashicorp/vault/helper/useragent"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"google.golang.org/api/iam/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
rolesetStoragePrefix = "roleset"
|
||||
)
|
||||
|
||||
func pathsRoleSet(b *backend) []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: fmt.Sprintf("roleset/%s", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Required. Name of the role.",
|
||||
},
|
||||
"secret_type": {
|
||||
Type: framework.TypeString,
|
||||
Description: fmt.Sprintf("Type of secret generated for this role set. Defaults to '%s'", SecretTypeAccessToken),
|
||||
Default: SecretTypeAccessToken,
|
||||
},
|
||||
"project": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the GCP project that this roleset's service account will belong to.",
|
||||
},
|
||||
"bindings": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Bindings configuration string.",
|
||||
},
|
||||
"token_scopes": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `List of OAuth scopes to assign to credentials generated under this role set`,
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathRoleSetExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.DeleteOperation: b.pathRoleSetDelete,
|
||||
logical.ReadOperation: b.pathRoleSetRead,
|
||||
logical.CreateOperation: b.pathRoleSetCreateUpdate,
|
||||
logical.UpdateOperation: b.pathRoleSetCreateUpdate,
|
||||
},
|
||||
HelpSynopsis: pathRoleSetHelpSyn,
|
||||
HelpDescription: pathRoleSetHelpDesc,
|
||||
},
|
||||
// Path to rotate role set service accounts
|
||||
{
|
||||
Pattern: fmt.Sprintf("roleset/%s/rotate", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathRoleSetExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleSetRotateAccount,
|
||||
},
|
||||
HelpSynopsis: pathRoleSetRotateHelpSyn,
|
||||
HelpDescription: pathRoleSetRotateHelpDesc,
|
||||
},
|
||||
// Path to rotating role set service account key used to generate access tokens
|
||||
{
|
||||
Pattern: fmt.Sprintf("roleset/%s/rotate-key", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathRoleSetExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleSetRotateKey,
|
||||
},
|
||||
HelpSynopsis: pathRoleSetRotateKeyHelpSyn,
|
||||
HelpDescription: pathRoleSetRotateKeyHelpDesc,
|
||||
},
|
||||
// Paths for listing role sets
|
||||
{
|
||||
Pattern: "rolesets/?",
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ListOperation: b.pathRoleSetList,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathListRoleSetHelpSyn,
|
||||
HelpDescription: pathListRoleSetHelpDesc,
|
||||
},
|
||||
{
|
||||
Pattern: "roleset/?",
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ListOperation: b.pathRoleSetList,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathListRoleSetHelpSyn,
|
||||
HelpDescription: pathListRoleSetHelpDesc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return false, errors.New("roleset name is required")
|
||||
}
|
||||
|
||||
rs, err := getRoleSet(nameRaw.(string), ctx, req.Storage)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return rs != nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("name is required"), nil
|
||||
}
|
||||
|
||||
rs, err := getRoleSet(nameRaw.(string), ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"secret_type": rs.SecretType,
|
||||
"bindings": rs.Bindings.asOutput(),
|
||||
}
|
||||
|
||||
if rs.AccountId != nil {
|
||||
data["service_account_email"] = rs.AccountId.EmailOrId
|
||||
data["service_account_project"] = rs.AccountId.Project
|
||||
}
|
||||
|
||||
if rs.TokenGen != nil && rs.SecretType == SecretTypeAccessToken {
|
||||
data["token_scopes"] = rs.TokenGen.Scopes
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("name is required"), nil
|
||||
}
|
||||
rsName := nameRaw.(string)
|
||||
|
||||
rs, err := getRoleSet(rsName, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("unable to get role set %s: {{err}}", rsName), err)
|
||||
}
|
||||
if rs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
if rs.AccountId != nil {
|
||||
_, err := framework.PutWAL(ctx, req.Storage, walTypeAccount, &walAccount{
|
||||
RoleSet: rsName,
|
||||
Id: *rs.AccountId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to create WAL entry to clean up service account: {{err}}", err)
|
||||
}
|
||||
|
||||
for resName, roleSet := range rs.Bindings {
|
||||
_, err := framework.PutWAL(ctx, req.Storage, walTypeIamPolicy, &walIamPolicy{
|
||||
RoleSet: rsName,
|
||||
AccountId: *rs.AccountId,
|
||||
Resource: resName,
|
||||
Roles: roleSet.ToSlice(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to create WAL entry to clean up service account bindings: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
if rs.TokenGen != nil {
|
||||
_, err := framework.PutWAL(ctx, req.Storage, walTypeAccount, &walAccountKey{
|
||||
RoleSet: rsName,
|
||||
ServiceAccountName: rs.AccountId.ResourceName(),
|
||||
KeyName: rs.TokenGen.KeyName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to create WAL entry to clean up service account key: {{err}}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := req.Storage.Delete(ctx, fmt.Sprintf("roleset/%s", nameRaw)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clean up resources:
|
||||
httpC, err := newHttpClient(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamAdmin, err := iam.New(httpC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamHandle := iamutil.GetIamHandle(httpC, useragent.String())
|
||||
|
||||
warnings := make([]string, 0)
|
||||
if rs.AccountId != nil {
|
||||
if err := b.deleteServiceAccount(ctx, iamAdmin, rs.AccountId); err != nil {
|
||||
w := fmt.Sprintf("unable to delete service account '%s' (WAL entry to clean-up later has been added): %v", rs.AccountId.ResourceName(), err)
|
||||
warnings = append(warnings, w)
|
||||
}
|
||||
if err := b.deleteTokenGenKey(ctx, iamAdmin, rs.TokenGen); err != nil {
|
||||
w := fmt.Sprintf("unable to delete key for service account '%s' (WAL entry to clean-up later has been added): %v", rs.AccountId.ResourceName(), err)
|
||||
warnings = append(warnings, w)
|
||||
}
|
||||
if merr := b.removeBindings(ctx, iamHandle, rs.AccountId.EmailOrId, rs.Bindings); merr != nil {
|
||||
for _, err := range merr.Errors {
|
||||
w := fmt.Sprintf("unable to delete IAM policy bindings for service account '%s' (WAL entry to clean-up later has been added): %v", rs.AccountId.EmailOrId, err)
|
||||
warnings = append(warnings, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
return &logical.Response{Warnings: warnings}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetCreateUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
var warnings []string
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("name is required"), nil
|
||||
}
|
||||
name := nameRaw.(string)
|
||||
|
||||
rs, err := getRoleSet(name, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rs == nil {
|
||||
rs = &RoleSet{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
isCreate := req.Operation == logical.CreateOperation
|
||||
|
||||
// Secret type
|
||||
if isCreate {
|
||||
secretType := d.Get("secret_type").(string)
|
||||
switch secretType {
|
||||
case SecretTypeKey, SecretTypeAccessToken:
|
||||
rs.SecretType = secretType
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf(`invalid "secret_type" value: "%s"`, secretType)), nil
|
||||
}
|
||||
} else {
|
||||
secretTypeRaw, ok := d.GetOk("secret_type")
|
||||
if ok && rs.SecretType != secretTypeRaw.(string) {
|
||||
return logical.ErrorResponse("cannot change secret_type after roleset creation"), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Project
|
||||
var project string
|
||||
projectRaw, ok := d.GetOk("project")
|
||||
if ok {
|
||||
project = projectRaw.(string)
|
||||
if !isCreate && rs.AccountId.Project != project {
|
||||
return logical.ErrorResponse(fmt.Sprintf("cannot change project for existing role set (old: %s, new: %s)", rs.AccountId.Project, project)), nil
|
||||
}
|
||||
if len(project) == 0 {
|
||||
return logical.ErrorResponse("given empty project"), nil
|
||||
}
|
||||
} else {
|
||||
if isCreate {
|
||||
return logical.ErrorResponse("project argument is required for new role set"), nil
|
||||
}
|
||||
project = rs.AccountId.Project
|
||||
}
|
||||
|
||||
// Default scopes
|
||||
var scopes []string
|
||||
scopesRaw, ok := d.GetOk("token_scopes")
|
||||
if ok {
|
||||
if rs.SecretType != SecretTypeAccessToken {
|
||||
warnings = []string{
|
||||
fmt.Sprintf("ignoring token_scopes, only valid for '%s' secret type role set", SecretTypeAccessToken),
|
||||
}
|
||||
}
|
||||
scopes = scopesRaw.([]string)
|
||||
if len(scopes) == 0 {
|
||||
return logical.ErrorResponse("cannot provide empty token_scopes"), nil
|
||||
}
|
||||
} else if rs.SecretType == SecretTypeAccessToken {
|
||||
if isCreate {
|
||||
return logical.ErrorResponse("token_scopes must be provided for creating access token role set"), nil
|
||||
}
|
||||
if rs.TokenGen != nil {
|
||||
scopes = rs.TokenGen.Scopes
|
||||
}
|
||||
}
|
||||
|
||||
// Bindings
|
||||
bRaw, newBindings := d.GetOk("bindings")
|
||||
if len(bRaw.(string)) == 0 {
|
||||
return logical.ErrorResponse("given empty bindings string"), nil
|
||||
}
|
||||
|
||||
if isCreate && newBindings == false {
|
||||
return logical.ErrorResponse("bindings are required for new role set"), nil
|
||||
}
|
||||
|
||||
if !newBindings {
|
||||
// Just save role with updated metadata:
|
||||
if err := rs.save(ctx, req.Storage); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If new bindings, update service account.
|
||||
var bindings ResourceBindings
|
||||
bindings, err = util.ParseBindings(bRaw.(string))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("unable to parse bindings: %v", err)), nil
|
||||
}
|
||||
if len(bindings) == 0 {
|
||||
return logical.ErrorResponse("unable to parse any bindings from given bindings HCL"), nil
|
||||
}
|
||||
rs.RawBindings = bRaw.(string)
|
||||
updateWarns, err := b.saveRoleSetWithNewAccount(ctx, req.Storage, rs, project, bindings, scopes)
|
||||
if updateWarns != nil {
|
||||
warnings = append(warnings, updateWarns...)
|
||||
}
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
} else if warnings != nil && len(warnings) > 0 {
|
||||
return &logical.Response{Warnings: warnings}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
rolesets, err := req.Storage.List(ctx, "roleset/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logical.ListResponse(rolesets), nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetRotateAccount(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("name is required"), nil
|
||||
}
|
||||
name := nameRaw.(string)
|
||||
|
||||
rs, err := getRoleSet(name, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("roleset '%s' not found", name)), nil
|
||||
}
|
||||
|
||||
var scopes []string
|
||||
if rs.TokenGen != nil {
|
||||
scopes = rs.TokenGen.Scopes
|
||||
}
|
||||
|
||||
warnings, err := b.saveRoleSetWithNewAccount(ctx, req.Storage, rs, rs.AccountId.Project, nil, scopes)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
} else if warnings != nil && len(warnings) > 0 {
|
||||
return &logical.Response{Warnings: warnings}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoleSetRotateKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
nameRaw, ok := d.GetOk("name")
|
||||
if !ok {
|
||||
return logical.ErrorResponse("name is required"), nil
|
||||
}
|
||||
name := nameRaw.(string)
|
||||
|
||||
rs, err := getRoleSet(name, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("roleset '%s' not found", name)), nil
|
||||
}
|
||||
|
||||
if rs.SecretType != SecretTypeAccessToken {
|
||||
return logical.ErrorResponse("cannot rotate key for non-access-token role set"), nil
|
||||
}
|
||||
var scopes []string
|
||||
if rs.TokenGen != nil {
|
||||
scopes = rs.TokenGen.Scopes
|
||||
}
|
||||
warn, err := b.saveRoleSetWithNewTokenKey(ctx, req.Storage, rs, scopes)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
if warn != "" {
|
||||
return &logical.Response{Warnings: []string{warn}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getRoleSet(name string, ctx context.Context, s logical.Storage) (*RoleSet, error) {
|
||||
entry, err := s.Get(ctx, fmt.Sprintf("%s/%s", rolesetStoragePrefix, name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rs := &RoleSet{}
|
||||
if err := entry.DecodeJSON(rs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
const pathRoleSetHelpSyn = `Read/write sets of IAM roles to be given to generated credentials for specified GCP resources.`
|
||||
const pathListRoleSetHelpSyn = `List existing rolesets.`
|
||||
const pathRoleSetRotateHelpSyn = `Rotate the service account (and key for access token roleset) created and used to generate secrets`
|
||||
const pathRoleSetRotateKeyHelpSyn = `Rotate only the service account key used by an access token roleset to generate tokens`
|
||||
|
||||
const pathRoleSetRotateHelpDesc = `
|
||||
This path allows you to rotate (i.e. recreate) the service account used to
|
||||
generate secrets for a given role set.`
|
||||
const pathRoleSetRotateKeyHelpDesc = `
|
||||
This path allows you to rotate (i.e. recreate) the service account
|
||||
key used to generate access tokens under a given role set. This
|
||||
path only applies to role sets that generate access tokens `
|
||||
|
||||
const pathRoleSetHelpDesc = `
|
||||
This path allows you create role sets, which bind sets of IAM roles
|
||||
to specific GCP resources. Secrets (either service account keys or
|
||||
access tokens) are generated under a role set and will have the
|
||||
given set of roles on resources.
|
||||
|
||||
The specified binding file accepts an HCL (or JSON) string
|
||||
with the following format:
|
||||
|
||||
resource "some/gcp/resource/uri" {
|
||||
roles = [
|
||||
"roles/role1",
|
||||
"roles/role2",
|
||||
"roles/role3",
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
The given resource can have the following
|
||||
|
||||
* Project-level self link
|
||||
Self-link for a resource under a given project
|
||||
(i.e. resource name starts with 'projects/...')
|
||||
Use if you need to provide a versioned object or
|
||||
are directly using resource.self_link.
|
||||
|
||||
Example (Compute instance):
|
||||
http://www.googleapis.com/compute/v1/projects/$PROJECT/zones/$ZONE/instances/$INSTANCE_NAME
|
||||
|
||||
* Full Resource Name
|
||||
A scheme-less URI consisting of a DNS-compatible
|
||||
API service name and a resource path (i.e. the
|
||||
relative resource name). Useful if you need to
|
||||
specify what service this resource is under
|
||||
but just want the preferred supported API version.
|
||||
Note that if the resource you are using is for
|
||||
a non-preferred API with multiple service versions,
|
||||
you MUST specify the version.
|
||||
|
||||
Example (IAM service account):
|
||||
//$SERVICE.googleapis.com/projects/my-project/serviceAccounts/myserviceaccount@...
|
||||
|
||||
* Relative Resource Name:
|
||||
A URI path (path-noscheme) without the leading "/".
|
||||
It identifies a resource within the API service.
|
||||
Use if there is only one service that your
|
||||
resource could belong to. If there are multiple
|
||||
API versions that support the resource, we will
|
||||
attempt to use the preferred version and ask
|
||||
for more specific format otherwise.
|
||||
|
||||
Example (Pubsub subscription):
|
||||
projects/myproject/subscriptions/mysub
|
||||
`
|
||||
const pathListRoleSetHelpDesc = `List role sets by role set name`
|
||||
412
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/role_set.go
generated
vendored
Normal file
412
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/role_set.go
generated
vendored
Normal file
@@ -0,0 +1,412 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util"
|
||||
"github.com/hashicorp/vault/helper/useragent"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceAccountMaxLen = 30
|
||||
serviceAccountDisplayNameTmpl = "Service account for Vault secrets backend role set %s"
|
||||
defaultCloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||
)
|
||||
|
||||
type RoleSet struct {
|
||||
Name string
|
||||
SecretType string
|
||||
|
||||
RawBindings string
|
||||
Bindings ResourceBindings
|
||||
|
||||
AccountId *gcputil.ServiceAccountId
|
||||
TokenGen *TokenGenerator
|
||||
}
|
||||
|
||||
func (rs *RoleSet) validate() error {
|
||||
var err *multierror.Error
|
||||
if rs.Name == "" {
|
||||
err = multierror.Append(err, errors.New("role set name is empty"))
|
||||
}
|
||||
|
||||
if rs.SecretType == "" {
|
||||
err = multierror.Append(err, errors.New("role set secret type is empty"))
|
||||
}
|
||||
|
||||
if rs.AccountId == nil {
|
||||
err = multierror.Append(err, fmt.Errorf("role set should have account associated"))
|
||||
}
|
||||
|
||||
if len(rs.Bindings) == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("role set bindings cannot be empty"))
|
||||
}
|
||||
|
||||
if len(rs.RawBindings) == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("role set raw bindings cannot be empty string"))
|
||||
}
|
||||
|
||||
switch rs.SecretType {
|
||||
case SecretTypeAccessToken:
|
||||
if rs.TokenGen == nil {
|
||||
err = multierror.Append(err, fmt.Errorf("access token role set should have initialized token generator"))
|
||||
} else if len(rs.TokenGen.Scopes) == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("access token role set should have defined scopes"))
|
||||
}
|
||||
case SecretTypeKey:
|
||||
break
|
||||
default:
|
||||
err = multierror.Append(err, fmt.Errorf("unknown secret type: %s", rs.SecretType))
|
||||
}
|
||||
return err.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (rs *RoleSet) save(ctx context.Context, s logical.Storage) error {
|
||||
if err := rs.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON(fmt.Sprintf("%s/%s", rolesetStoragePrefix, rs.Name), rs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Put(ctx, entry)
|
||||
}
|
||||
|
||||
func (rs *RoleSet) bindingHash() string {
|
||||
ssum := sha256.Sum256([]byte(rs.RawBindings)[:])
|
||||
return base64.StdEncoding.EncodeToString(ssum[:])
|
||||
}
|
||||
|
||||
func (rs *RoleSet) getServiceAccount(iamAdmin *iam.Service) (*iam.ServiceAccount, error) {
|
||||
if rs.AccountId == nil {
|
||||
return nil, fmt.Errorf("role set '%s' is invalid, has no associated service account", rs.Name)
|
||||
}
|
||||
|
||||
account, err := iamAdmin.Projects.ServiceAccounts.Get(rs.AccountId.ResourceName()).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find service account: %v. If account was deleted, role set must be updated (write to roleset/%s/rotate) before generating new secrets", err, rs.Name)
|
||||
} else if account == nil {
|
||||
return nil, fmt.Errorf("roleset service account was removed - role set must be updated (path roleset/%s/rotate) before generating new secrets", rs.Name)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
type ResourceBindings map[string]util.StringSet
|
||||
|
||||
func (rb ResourceBindings) asOutput() map[string][]string {
|
||||
out := make(map[string][]string)
|
||||
for k, v := range rb {
|
||||
out[k] = v.ToSlice()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type TokenGenerator struct {
|
||||
KeyName string
|
||||
B64KeyJSON string
|
||||
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
func (b *backend) saveRoleSetWithNewAccount(ctx context.Context, s logical.Storage, rs *RoleSet, project string, newBinds ResourceBindings, scopes []string) (warning []string, err error) {
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
httpC, err := newHttpClient(ctx, s, defaultCloudPlatformScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamAdmin, err := newIamAdmin(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamHandle := iamutil.GetIamHandle(httpC, useragent.String())
|
||||
|
||||
oldAccount := rs.AccountId
|
||||
oldBindings := rs.Bindings
|
||||
oldTokenKey := rs.TokenGen
|
||||
|
||||
oldWals, err := rs.addWALsForCurrentAccount(ctx, s)
|
||||
if err != nil {
|
||||
tryDeleteWALs(ctx, s, oldWals...)
|
||||
return nil, errwrap.Wrapf("failed to create WAL for cleaning up old account: {{err}}", err)
|
||||
}
|
||||
|
||||
newWals := make([]string, 0, len(newBinds)+2)
|
||||
walId, err := rs.newServiceAccount(ctx, s, iamAdmin, project)
|
||||
if err != nil {
|
||||
tryDeleteWALs(ctx, s, oldWals...)
|
||||
return nil, err
|
||||
}
|
||||
newWals = append(newWals, walId)
|
||||
|
||||
binds := rs.Bindings
|
||||
if newBinds != nil {
|
||||
binds = newBinds
|
||||
rs.Bindings = newBinds
|
||||
}
|
||||
walIds, err := rs.updateIamPolicies(ctx, s, b.enabledIamResources, iamHandle, binds)
|
||||
if err != nil {
|
||||
tryDeleteWALs(ctx, s, oldWals...)
|
||||
return nil, err
|
||||
}
|
||||
newWals = append(newWals, walIds...)
|
||||
|
||||
if rs.SecretType == SecretTypeAccessToken {
|
||||
walId, err := rs.newKeyForTokenGen(ctx, s, iamAdmin, scopes)
|
||||
if err != nil {
|
||||
tryDeleteWALs(ctx, s, oldWals...)
|
||||
return nil, err
|
||||
}
|
||||
newWals = append(newWals, walId)
|
||||
}
|
||||
|
||||
if err := rs.save(ctx, s); err != nil {
|
||||
tryDeleteWALs(ctx, s, oldWals...)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete WALs for cleaning up new resources now that they have been saved.
|
||||
tryDeleteWALs(ctx, s, newWals...)
|
||||
|
||||
// Try deleting old resources (WALs exist so we can ignore failures)
|
||||
if oldAccount == nil || oldAccount.EmailOrId == "" {
|
||||
// nothing to clean up
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Return any errors as warnings so user knows immediate cleanup failed
|
||||
warnings := make([]string, 0)
|
||||
if errs := b.removeBindings(ctx, iamHandle, oldAccount.EmailOrId, oldBindings); errs != nil {
|
||||
warnings = make([]string, len(errs.Errors), len(errs.Errors)+2)
|
||||
for idx, err := range errs.Errors {
|
||||
warnings[idx] = fmt.Sprintf("unable to immediately delete old binding (WAL cleanup entry has been added): %v", err)
|
||||
}
|
||||
}
|
||||
if err := b.deleteServiceAccount(ctx, iamAdmin, oldAccount); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("unable to immediately delete old account (WAL cleanup entry has been added): %v", err))
|
||||
}
|
||||
if err := b.deleteTokenGenKey(ctx, iamAdmin, oldTokenKey); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("unable to immediately delete old key (WAL cleanup entry has been added): %v", err))
|
||||
}
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *backend) saveRoleSetWithNewTokenKey(ctx context.Context, s logical.Storage, rs *RoleSet, scopes []string) (warning string, err error) {
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
if rs.SecretType != SecretTypeAccessToken {
|
||||
return "", fmt.Errorf("a key is not saved or used for non-access-token role set '%s'", rs.Name)
|
||||
}
|
||||
|
||||
iamAdmin, err := newIamAdmin(ctx, s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oldKeyWalId := ""
|
||||
if rs.TokenGen != nil {
|
||||
if oldKeyWalId, err = framework.PutWAL(ctx, s, walTypeAccountKey, &walAccountKey{
|
||||
RoleSet: rs.Name,
|
||||
KeyName: rs.TokenGen.KeyName,
|
||||
ServiceAccountName: rs.AccountId.ResourceName(),
|
||||
}); err != nil {
|
||||
return "", errwrap.Wrapf("unable to create WAL for deleting old key: {{err}}", err)
|
||||
}
|
||||
}
|
||||
oldKeyGen := rs.TokenGen
|
||||
|
||||
newKeyWalId, err := rs.newKeyForTokenGen(ctx, s, iamAdmin, scopes)
|
||||
if err != nil {
|
||||
tryDeleteWALs(ctx, s, oldKeyWalId)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := rs.save(ctx, s); err != nil {
|
||||
tryDeleteWALs(ctx, s, oldKeyWalId)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Delete WALs for cleaning up new key now that it's been saved.
|
||||
tryDeleteWALs(ctx, s, newKeyWalId)
|
||||
if err := b.deleteTokenGenKey(ctx, iamAdmin, oldKeyGen); err != nil {
|
||||
return errwrap.Wrapf("unable to delete old key (delayed cleaned up WAL entry added): {{err}}", err).Error(), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (rs *RoleSet) addWALsForCurrentAccount(ctx context.Context, s logical.Storage) ([]string, error) {
|
||||
if rs.AccountId == nil {
|
||||
return nil, nil
|
||||
}
|
||||
wals := make([]string, 0, len(rs.Bindings)+2)
|
||||
walId, err := framework.PutWAL(ctx, s, walTypeAccount, &walAccount{
|
||||
RoleSet: rs.Name,
|
||||
Id: gcputil.ServiceAccountId{
|
||||
Project: rs.AccountId.Project,
|
||||
EmailOrId: rs.AccountId.EmailOrId,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wals = append(wals, walId)
|
||||
for resource, roles := range rs.Bindings {
|
||||
var walId string
|
||||
walId, err = framework.PutWAL(ctx, s, walTypeIamPolicy, &walIamPolicy{
|
||||
RoleSet: rs.Name,
|
||||
AccountId: gcputil.ServiceAccountId{
|
||||
Project: rs.AccountId.Project,
|
||||
EmailOrId: rs.AccountId.EmailOrId,
|
||||
},
|
||||
Resource: resource,
|
||||
Roles: roles.ToSlice(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wals = append(wals, walId)
|
||||
}
|
||||
|
||||
if rs.SecretType == SecretTypeAccessToken && rs.TokenGen != nil {
|
||||
walId, err := framework.PutWAL(ctx, s, walTypeAccountKey, &walAccountKey{
|
||||
RoleSet: rs.Name,
|
||||
KeyName: rs.TokenGen.KeyName,
|
||||
ServiceAccountName: rs.AccountId.ResourceName(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wals = append(wals, walId)
|
||||
}
|
||||
return wals, nil
|
||||
}
|
||||
|
||||
func (rs *RoleSet) newServiceAccount(ctx context.Context, s logical.Storage, iamAdmin *iam.Service, project string) (string, error) {
|
||||
saEmailPrefix := roleSetServiceAccountName(rs.Name)
|
||||
projectName := fmt.Sprintf("projects/%s", project)
|
||||
displayName := fmt.Sprintf(serviceAccountDisplayNameTmpl, rs.Name)
|
||||
|
||||
walId, err := framework.PutWAL(ctx, s, walTypeAccount, &walAccount{
|
||||
RoleSet: rs.Name,
|
||||
Id: gcputil.ServiceAccountId{
|
||||
Project: project,
|
||||
EmailOrId: fmt.Sprintf("%s@%s.iam.gserviceaccount.com", saEmailPrefix, project),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("unable to create WAL entry for generating new service account: {{err}}", err)
|
||||
}
|
||||
|
||||
sa, err := iamAdmin.Projects.ServiceAccounts.Create(
|
||||
projectName, &iam.CreateServiceAccountRequest{
|
||||
AccountId: saEmailPrefix,
|
||||
ServiceAccount: &iam.ServiceAccount{DisplayName: displayName},
|
||||
}).Do()
|
||||
if err != nil {
|
||||
return walId, errwrap.Wrapf(fmt.Sprintf("unable to create new service account under project '%s': {{err}}", projectName), err)
|
||||
}
|
||||
rs.AccountId = &gcputil.ServiceAccountId{
|
||||
Project: project,
|
||||
EmailOrId: sa.Email,
|
||||
}
|
||||
return walId, nil
|
||||
}
|
||||
|
||||
func (rs *RoleSet) newKeyForTokenGen(ctx context.Context, s logical.Storage, iamAdmin *iam.Service, scopes []string) (string, error) {
|
||||
walId, err := framework.PutWAL(ctx, s, walTypeAccountKey, &walAccountKey{
|
||||
RoleSet: rs.Name,
|
||||
KeyName: "",
|
||||
ServiceAccountName: rs.AccountId.ResourceName(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := iamAdmin.Projects.ServiceAccounts.Keys.Create(rs.AccountId.ResourceName(),
|
||||
&iam.CreateServiceAccountKeyRequest{
|
||||
PrivateKeyType: privateKeyTypeJson,
|
||||
}).Do()
|
||||
if err != nil {
|
||||
framework.DeleteWAL(ctx, s, walId)
|
||||
return "", err
|
||||
}
|
||||
rs.TokenGen = &TokenGenerator{
|
||||
KeyName: key.Name,
|
||||
B64KeyJSON: key.PrivateKeyData,
|
||||
Scopes: scopes,
|
||||
}
|
||||
return walId, nil
|
||||
}
|
||||
|
||||
func (rs *RoleSet) updateIamPolicies(ctx context.Context, s logical.Storage, enabledIamResources iamutil.EnabledResources, iamHandle *iamutil.IamHandle, rb ResourceBindings) ([]string, error) {
|
||||
wals := make([]string, 0, len(rb))
|
||||
for rName, roles := range rb {
|
||||
walId, err := framework.PutWAL(ctx, s, walTypeIamPolicy, &walIamPolicy{
|
||||
RoleSet: rs.Name,
|
||||
AccountId: gcputil.ServiceAccountId{
|
||||
Project: rs.AccountId.Project,
|
||||
EmailOrId: rs.AccountId.EmailOrId,
|
||||
},
|
||||
Resource: rName,
|
||||
Roles: roles.ToSlice(),
|
||||
})
|
||||
if err != nil {
|
||||
return wals, err
|
||||
}
|
||||
|
||||
resource, err := enabledIamResources.Resource(rName)
|
||||
if err != nil {
|
||||
return wals, err
|
||||
}
|
||||
|
||||
p, err := iamHandle.GetIamPolicy(ctx, resource)
|
||||
if err != nil {
|
||||
return wals, err
|
||||
}
|
||||
|
||||
changed, newP := p.AddBindings(&iamutil.PolicyDelta{
|
||||
Roles: roles,
|
||||
Email: rs.AccountId.EmailOrId,
|
||||
})
|
||||
if !changed || newP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := iamHandle.SetIamPolicy(ctx, resource, newP); err != nil {
|
||||
return wals, err
|
||||
}
|
||||
wals = append(wals, walId)
|
||||
}
|
||||
return wals, nil
|
||||
}
|
||||
|
||||
func roleSetServiceAccountName(rsName string) (name string) {
|
||||
intSuffix := fmt.Sprintf("%d", time.Now().Unix())
|
||||
fullName := fmt.Sprintf("vault%s-%s", rsName, intSuffix)
|
||||
|
||||
name = fullName
|
||||
if len(fullName) > serviceAccountMaxLen {
|
||||
toTrunc := len(fullName) - serviceAccountMaxLen
|
||||
name = fmt.Sprintf("vault%s-%s", rsName[:len(rsName)-toTrunc], intSuffix)
|
||||
}
|
||||
return name
|
||||
}
|
||||
276
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/rollback.go
generated
vendored
Normal file
276
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/rollback.go
generated
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil"
|
||||
"github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util"
|
||||
"github.com/hashicorp/vault/helper/useragent"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iam/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
walTypeAccount = "account"
|
||||
walTypeAccountKey = "account_key"
|
||||
walTypeIamPolicy = "iam_policy"
|
||||
)
|
||||
|
||||
func (b *backend) walRollback(ctx context.Context, req *logical.Request, kind string, data interface{}) error {
|
||||
switch kind {
|
||||
case walTypeAccount:
|
||||
return b.serviceAccountRollback(ctx, req, data)
|
||||
case walTypeAccountKey:
|
||||
return b.serviceAccountKeyRollback(ctx, req, data)
|
||||
case walTypeIamPolicy:
|
||||
return b.serviceAccountPolicyRollback(ctx, req, data)
|
||||
default:
|
||||
return fmt.Errorf("unknown type to rollback")
|
||||
}
|
||||
}
|
||||
|
||||
type walAccount struct {
|
||||
RoleSet string
|
||||
Id gcputil.ServiceAccountId
|
||||
}
|
||||
|
||||
type walAccountKey struct {
|
||||
RoleSet string
|
||||
ServiceAccountName string
|
||||
KeyName string
|
||||
}
|
||||
|
||||
type walIamPolicy struct {
|
||||
RoleSet string
|
||||
AccountId gcputil.ServiceAccountId
|
||||
Resource string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (b *backend) serviceAccountRollback(ctx context.Context, req *logical.Request, data interface{}) error {
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
var entry walAccount
|
||||
if err := mapstructure.Decode(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If account is still being used, WAL entry was not
|
||||
// deleted properly after a successful operation.
|
||||
// Remove WAL entry.
|
||||
rs, err := getRoleSet(entry.RoleSet, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs != nil && entry.Id.ResourceName() == rs.AccountId.ResourceName() {
|
||||
// Still being used - don't delete this service account.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete service account.
|
||||
iamC, err := newIamAdmin(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.deleteServiceAccount(ctx, iamC, &entry.Id)
|
||||
}
|
||||
|
||||
func (b *backend) serviceAccountKeyRollback(ctx context.Context, req *logical.Request, data interface{}) error {
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
var entry walAccountKey
|
||||
if err := mapstructure.Decode(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If key is still being used, WAL entry was not
|
||||
// deleted properly after a successful operation.
|
||||
// Remove WAL entry.
|
||||
rs, err := getRoleSet(entry.RoleSet, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs != nil && rs.TokenGen != nil && entry.KeyName == rs.TokenGen.KeyName {
|
||||
return nil
|
||||
}
|
||||
|
||||
iamC, err := newIamAdmin(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.KeyName == "" {
|
||||
if rs.SecretType != SecretTypeAccessToken {
|
||||
// Do not clean up non-access-token role set keys.
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete all keys not in use by role set
|
||||
keys, err := iamC.Projects.ServiceAccounts.Keys.List(rs.AccountId.ResourceName()).KeyTypes("USER_MANAGED").Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return err
|
||||
} else if err != nil || keys == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, k := range keys.Keys {
|
||||
if rs.TokenGen != nil && rs.TokenGen.KeyName == k.Name {
|
||||
continue
|
||||
}
|
||||
// Delete all keys not being used by role set
|
||||
_, err = iamC.Projects.ServiceAccounts.Keys.Delete(entry.KeyName).Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = iamC.Projects.ServiceAccounts.Keys.Delete(entry.KeyName).Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical.Request, data interface{}) error {
|
||||
b.rolesetLock.Lock()
|
||||
defer b.rolesetLock.Unlock()
|
||||
|
||||
var entry walIamPolicy
|
||||
if err := mapstructure.Decode(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to verify service account not being used by roleset
|
||||
rs, err := getRoleSet(entry.RoleSet, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Take our any bindings still being used by this role set from roles being removed.
|
||||
rolesToRemove := util.ToSet(entry.Roles)
|
||||
if rs.AccountId.ResourceName() == entry.AccountId.ResourceName() {
|
||||
currRoles, ok := rs.Bindings[entry.Resource]
|
||||
if ok {
|
||||
rolesToRemove = rolesToRemove.Sub(currRoles)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := b.enabledIamResources.Resource(entry.Resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpC, err := newHttpClient(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iamHandle := iamutil.GetIamHandle(httpC, useragent.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := iamHandle.GetIamPolicy(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changed, newP := p.RemoveBindings(
|
||||
&iamutil.PolicyDelta{
|
||||
Email: entry.AccountId.EmailOrId,
|
||||
Roles: rolesToRemove,
|
||||
})
|
||||
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = iamHandle.SetIamPolicy(ctx, r, newP)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *backend) deleteServiceAccount(ctx context.Context, iamAdmin *iam.Service, account *gcputil.ServiceAccountId) error {
|
||||
if account == nil || account.EmailOrId == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := iamAdmin.Projects.ServiceAccounts.Delete(account.ResourceName()).Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return errwrap.Wrapf("unable to delete service account: {{err}}", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) deleteTokenGenKey(ctx context.Context, iamAdmin *iam.Service, tgen *TokenGenerator) error {
|
||||
if tgen == nil || tgen.KeyName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := iamAdmin.Projects.ServiceAccounts.Keys.Delete(tgen.KeyName).Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return errwrap.Wrapf("unable to delete service account key: {{err}}", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) removeBindings(ctx context.Context, iamHandle *iamutil.IamHandle, email string, bindings ResourceBindings) (allErr *multierror.Error) {
|
||||
for resName, roles := range bindings {
|
||||
resource, err := b.enabledIamResources.Resource(resName)
|
||||
if err != nil {
|
||||
allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err))
|
||||
continue
|
||||
}
|
||||
|
||||
p, err := iamHandle.GetIamPolicy(ctx, resource)
|
||||
if err != nil {
|
||||
allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err))
|
||||
continue
|
||||
}
|
||||
|
||||
changed, newP := p.RemoveBindings(&iamutil.PolicyDelta{
|
||||
Email: email,
|
||||
Roles: roles,
|
||||
})
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
if _, err = iamHandle.SetIamPolicy(ctx, resource, newP); err != nil {
|
||||
allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// This tries to clean up WALs that are no longer needed.
|
||||
// We can ignore errors if deletion fails as WAL rollback
|
||||
// will not be done if the object is still in use in the roleset
|
||||
// or was not actually created.
|
||||
func tryDeleteWALs(ctx context.Context, s logical.Storage, walIds ...string) {
|
||||
for _, walId := range walIds {
|
||||
// ignore errors - if not deleted and still used by
|
||||
// roleset, will be ignored
|
||||
framework.DeleteWAL(ctx, s, walId)
|
||||
}
|
||||
}
|
||||
|
||||
func isGoogleApi404Error(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
gErr, ok := err.(*googleapi.Error)
|
||||
if ok && gErr.Code == 404 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
162
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/secrets_access_token.go
generated
vendored
Normal file
162
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/secrets_access_token.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SecretTypeAccessToken = "access_token"
|
||||
revokeAccessTokenEndpoint = "https://accounts.google.com/o/oauth2/revoke"
|
||||
revokeTokenWarning = `revocation request was successful; however, due to how OAuth access propagation works, the OAuth token might still be valid until it expires`
|
||||
)
|
||||
|
||||
func secretAccessToken(b *backend) *framework.Secret {
|
||||
return &framework.Secret{
|
||||
Type: SecretTypeAccessToken,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"token": {
|
||||
Type: framework.TypeString,
|
||||
Description: "OAuth2 token",
|
||||
},
|
||||
},
|
||||
Renew: b.secretAccessTokenRenew,
|
||||
Revoke: secretAccessTokenRevoke,
|
||||
}
|
||||
}
|
||||
|
||||
func pathSecretAccessToken(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: fmt.Sprintf("token/%s", framework.GenericNameRegex("roleset")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"roleset": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Required. Name of the role set.",
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathRoleSetExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathAccessToken,
|
||||
logical.UpdateOperation: b.pathAccessToken,
|
||||
},
|
||||
HelpSynopsis: pathTokenHelpSyn,
|
||||
HelpDescription: pathTokenHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathAccessToken(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
rsName := d.Get("roleset").(string)
|
||||
|
||||
rs, err := getRoleSet(rsName, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role set '%s' does not exists", rsName)), nil
|
||||
}
|
||||
|
||||
if rs.SecretType != SecretTypeAccessToken {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role set '%s' cannot generate access tokens (has secret type %s)", rsName, rs.SecretType)), nil
|
||||
}
|
||||
|
||||
return b.getSecretAccessToken(ctx, req.Storage, rs)
|
||||
}
|
||||
|
||||
func (b *backend) secretAccessTokenRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
// Renewal not allowed
|
||||
return logical.ErrorResponse("short-term access tokens cannot be renewed - request new access token instead"), nil
|
||||
}
|
||||
|
||||
func secretAccessTokenRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
tokenRaw, ok := req.Secret.InternalData["access_token"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret is missing token internal data")
|
||||
}
|
||||
|
||||
resp, err := http.Get(revokeAccessTokenEndpoint + fmt.Sprintf("?token=%s", url.QueryEscape(tokenRaw.(string))))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("revoke returned error: %v", err)), nil
|
||||
}
|
||||
if err := googleapi.CheckResponse(resp); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Warnings: []string{revokeTokenWarning},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) getSecretAccessToken(ctx context.Context, s logical.Storage, rs *RoleSet) (*logical.Response, error) {
|
||||
iamC, err := newIamAdmin(ctx, s)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not create IAM Admin client: {{err}}", err)
|
||||
}
|
||||
|
||||
// Verify account still exists
|
||||
_, err = rs.getServiceAccount(iamC)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("could not get role set service account: %v", err)), nil
|
||||
}
|
||||
|
||||
if rs.TokenGen == nil || rs.TokenGen.KeyName == "" {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid role set has no service account key, must be updated (path roleset/%s/rotate-key) before generating new secrets", rs.Name)), nil
|
||||
}
|
||||
|
||||
token, err := rs.TokenGen.getAccessToken(ctx, iamC)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("could not generate token: %v", err)), nil
|
||||
}
|
||||
|
||||
secretD := map[string]interface{}{
|
||||
"token": token.AccessToken,
|
||||
}
|
||||
internalD := map[string]interface{}{
|
||||
"access_token": token.AccessToken,
|
||||
"key_name": rs.TokenGen.KeyName,
|
||||
"role_set": rs.Name,
|
||||
"role_set_bindings": rs.bindingHash(),
|
||||
}
|
||||
resp := b.Secret(SecretTypeAccessToken).Response(secretD, internalD)
|
||||
resp.Secret.LeaseOptions.TTL = token.Expiry.Sub(time.Now())
|
||||
resp.Secret.LeaseOptions.Renewable = false
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (tg *TokenGenerator) getAccessToken(ctx context.Context, iamAdmin *iam.Service) (*oauth2.Token, error) {
|
||||
key, err := iamAdmin.Projects.ServiceAccounts.Keys.Get(tg.KeyName).Do()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not verify key used to generate tokens: {{err}}", err)
|
||||
}
|
||||
if key == nil {
|
||||
return nil, errors.New("could not find key used to generate tokens, must update role set")
|
||||
}
|
||||
|
||||
jsonBytes, err := base64.StdEncoding.DecodeString(tg.B64KeyJSON)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not b64-decode key data: {{err}}", err)
|
||||
}
|
||||
|
||||
cfg, err := google.JWTConfigFromJSON(jsonBytes, tg.Scopes...)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not generate token JWT config: {{err}}", err)
|
||||
}
|
||||
|
||||
tkn, err := cfg.TokenSource(ctx).Token()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not generate token: {{err}}", err)
|
||||
}
|
||||
return tkn, err
|
||||
}
|
||||
245
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/secrets_service_account_key.go
generated
vendored
Normal file
245
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/secrets_service_account_key.go
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
package gcpsecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SecretTypeKey = "service_account_key"
|
||||
keyAlgorithmRSA2k = "KEY_ALG_RSA_2048"
|
||||
privateKeyTypeJson = "TYPE_GOOGLE_CREDENTIALS_FILE"
|
||||
)
|
||||
|
||||
func secretServiceAccountKey(b *backend) *framework.Secret {
|
||||
return &framework.Secret{
|
||||
Type: SecretTypeKey,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"private_key_data": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Base-64 encoded string. Private key data for a service account key",
|
||||
},
|
||||
"key_algorithm": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Which type of key and algorithm to use for the key (defaults to 2K RSA). Valid values are GCP enum(ServiceAccountKeyAlgorithm)",
|
||||
},
|
||||
"key_type": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Type of the private key (i.e. whether it is JSON or P12). Valid values are GCP enum(ServiceAccountPrivateKeyType)",
|
||||
},
|
||||
},
|
||||
|
||||
Renew: b.secretKeyRenew,
|
||||
Revoke: secretKeyRevoke,
|
||||
}
|
||||
}
|
||||
|
||||
func pathSecretServiceAccountKey(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: fmt.Sprintf("key/%s", framework.GenericNameRegex("roleset")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"roleset": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Required. Name of the role set.",
|
||||
},
|
||||
"key_algorithm": {
|
||||
Type: framework.TypeString,
|
||||
Description: fmt.Sprintf(`Private key algorithm for service account key - defaults to %s"`, keyAlgorithmRSA2k),
|
||||
Default: keyAlgorithmRSA2k,
|
||||
},
|
||||
"key_type": {
|
||||
Type: framework.TypeString,
|
||||
Description: fmt.Sprintf(`Private key type for service account key - defaults to %s"`, privateKeyTypeJson),
|
||||
Default: privateKeyTypeJson,
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.pathRoleSetExistenceCheck,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathServiceAccountKey,
|
||||
logical.UpdateOperation: b.pathServiceAccountKey,
|
||||
},
|
||||
HelpSynopsis: pathServiceAccountKeySyn,
|
||||
HelpDescription: pathServiceAccountKeyDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathServiceAccountKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
rsName := d.Get("roleset").(string)
|
||||
keyType := d.Get("key_type").(string)
|
||||
keyAlg := d.Get("key_algorithm").(string)
|
||||
|
||||
rs, err := getRoleSet(rsName, ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role set '%s' does not exists", rsName)), nil
|
||||
}
|
||||
|
||||
if rs.SecretType != SecretTypeKey {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role set '%s' cannot generate service account keys (has secret type %s)", rsName, rs.SecretType)), nil
|
||||
}
|
||||
|
||||
return b.getSecretKey(ctx, req.Storage, rs, keyType, keyAlg)
|
||||
}
|
||||
|
||||
func (b *backend) secretKeyRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
resp, err := b.verifySecretServiceKeyExists(ctx, req)
|
||||
if err != nil || resp != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
cfg, err := getConfig(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
cfg = &config{}
|
||||
}
|
||||
|
||||
f := framework.LeaseExtend(cfg.TTL, cfg.MaxTTL, b.System())
|
||||
return f(ctx, req, d)
|
||||
}
|
||||
|
||||
func (b *backend) verifySecretServiceKeyExists(ctx context.Context, req *logical.Request) (*logical.Response, error) {
|
||||
keyName, ok := req.Secret.InternalData["key_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid secret, internal data is missing key name")
|
||||
}
|
||||
|
||||
rsName, ok := req.Secret.InternalData["role_set"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid secret, internal data is missing role set name")
|
||||
}
|
||||
|
||||
bindingSum, ok := req.Secret.InternalData["role_set_bindings"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid secret, internal data is missing role set checksum")
|
||||
}
|
||||
|
||||
// Verify role set was not deleted.
|
||||
rs, err := getRoleSet(rsName.(string), ctx, req.Storage)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("could not find role set '%v' for secret", rsName)), nil
|
||||
}
|
||||
|
||||
// Verify role set bindings have not changed since secret was generated.
|
||||
if rs.bindingHash() != bindingSum.(string) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role set '%v' bindings were updated since secret was generated, cannot renew", rsName)), nil
|
||||
}
|
||||
|
||||
// Verify service account key still exists.
|
||||
iamAdmin, err := newIamAdmin(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("could not confirm key still exists in GCP"), nil
|
||||
}
|
||||
if k, err := iamAdmin.Projects.ServiceAccounts.Keys.Get(keyName.(string)).Do(); err != nil || k == nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("could not confirm key still exists in GCP: %v", err)), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func secretKeyRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
keyNameRaw, ok := req.Secret.InternalData["key_name"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret is missing key_name internal data")
|
||||
}
|
||||
|
||||
iamAdmin, err := newIamAdmin(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
_, err = iamAdmin.Projects.ServiceAccounts.Keys.Delete(keyNameRaw.(string)).Do()
|
||||
if err != nil && !isGoogleApi404Error(err) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("unable to delete service account key: %v", err)), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) getSecretKey(ctx context.Context, s logical.Storage, rs *RoleSet, keyType, keyAlgorithm string) (*logical.Response, error) {
|
||||
var ttl time.Duration
|
||||
cfg, err := getConfig(ctx, s)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not read backend config: {{err}}", err)
|
||||
}
|
||||
max := b.System().MaxLeaseTTL()
|
||||
if cfg == nil {
|
||||
ttl = b.System().DefaultLeaseTTL()
|
||||
} else {
|
||||
if cfg.MaxTTL != 0 && cfg.MaxTTL < max {
|
||||
max = cfg.MaxTTL
|
||||
}
|
||||
if cfg.TTL > 0 {
|
||||
ttl = cfg.TTL
|
||||
}
|
||||
}
|
||||
|
||||
if ttl > max {
|
||||
ttl = max
|
||||
}
|
||||
|
||||
iamC, err := newIamAdmin(ctx, s)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("could not create IAM Admin client: {{err}}", err)
|
||||
}
|
||||
|
||||
account, err := rs.getServiceAccount(iamC)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("roleset service account was removed - role set must be updated (write to roleset/%s/rotate) before generating new secrets", rs.Name)), nil
|
||||
}
|
||||
|
||||
key, err := iamC.Projects.ServiceAccounts.Keys.Create(
|
||||
account.Name, &iam.CreateServiceAccountKeyRequest{
|
||||
KeyAlgorithm: keyAlgorithm,
|
||||
PrivateKeyType: keyType,
|
||||
}).Do()
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
secretD := map[string]interface{}{
|
||||
"private_key_data": key.PrivateKeyData,
|
||||
"key_algorithm": key.KeyAlgorithm,
|
||||
"key_type": key.PrivateKeyType,
|
||||
}
|
||||
internalD := map[string]interface{}{
|
||||
"key_name": key.Name,
|
||||
"role_set": rs.Name,
|
||||
"role_set_bindings": rs.bindingHash(),
|
||||
}
|
||||
|
||||
resp := b.Secret(SecretTypeKey).Response(secretD, internalD)
|
||||
resp.Secret.LeaseOptions.TTL = ttl
|
||||
resp.Secret.LeaseOptions.Renewable = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
const pathTokenHelpSyn = `Generate an OAuth2 access token under a specific role set.`
|
||||
const pathTokenHelpDesc = `
|
||||
This path will generate a new OAuth2 access token for accessing GCP APIs.
|
||||
A role set, binding IAM roles to specific GCP resources, will be specified
|
||||
by name - for example, if this backend is mounted at "gcp",
|
||||
then "gcp/token/deploy" would generate tokens for the "deploy" role set.
|
||||
|
||||
On the backend, each roleset is associated with a service account.
|
||||
The token will be associated with this service account. Tokens have a
|
||||
short-term lease (1-hour) associated with them but cannot be renewed.
|
||||
`
|
||||
|
||||
const pathServiceAccountKeySyn = `Generate an service account private key under a specific role set.`
|
||||
const pathServiceAccountKeyDesc = `
|
||||
This path will generate a new service account private key for accessing GCP APIs.
|
||||
A role set, binding IAM roles to specific GCP resources, will be specified
|
||||
by name - for example, if this backend is mounted at "gcp", then "gcp/key/deploy"
|
||||
would generate service account keys for the "deploy" role set.
|
||||
|
||||
On the backend, each roleset is associated with a service account under
|
||||
which secrets/keys are created.
|
||||
`
|
||||
12
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/bindings_template
generated
vendored
Normal file
12
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/bindings_template
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{{define "bindings" -}}
|
||||
{{ range $resource,$roleStringSet := . -}}
|
||||
resource "{{$resource}}" {
|
||||
roles = [
|
||||
{{- range $role, $v := $roleStringSet -}}
|
||||
"{{ $role }}",
|
||||
{{- end -}}
|
||||
],
|
||||
}
|
||||
|
||||
{{ end -}}
|
||||
{{- end }}
|
||||
135
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/parse_bindings.go
generated
vendored
Normal file
135
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/parse_bindings.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const bindingTemplate = "util/bindings_template"
|
||||
|
||||
func BindingsHCL(bindings map[string]StringSet) (string, error) {
|
||||
tpl, err := template.ParseFiles(bindingTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tpl.ExecuteTemplate(&buf, "bindings", bindings); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func ParseBindings(bindingsStr string) (map[string]StringSet, error) {
|
||||
// Try to base64 decode
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bindingsStr))
|
||||
decoded, b64err := ioutil.ReadAll(decoder)
|
||||
|
||||
var bindsString string
|
||||
if b64err != nil {
|
||||
bindsString = bindingsStr
|
||||
} else {
|
||||
bindsString = string(decoded)
|
||||
}
|
||||
|
||||
root, err := hcl.Parse(bindsString)
|
||||
if err != nil {
|
||||
if b64err == nil {
|
||||
return nil, errwrap.Wrapf("unable to parse base64-encoded bindings as valid HCL: {{err}}", err)
|
||||
} else {
|
||||
return nil, errwrap.Wrapf("unable to parse raw string bindings as valid HCL: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
bindingLst, ok := root.Node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to parse bindings: does not contain a root object")
|
||||
}
|
||||
|
||||
bindingsMap, err := parseBindingObjList(bindingLst)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to parse bindings: {{err}}", err)
|
||||
}
|
||||
return bindingsMap, nil
|
||||
}
|
||||
|
||||
func parseBindingObjList(topList *ast.ObjectList) (map[string]StringSet, error) {
|
||||
var merr *multierror.Error
|
||||
|
||||
bindings := make(map[string]StringSet)
|
||||
|
||||
for _, item := range topList.Items {
|
||||
if len(item.Keys) != 2 {
|
||||
merr = multierror.Append(merr, fmt.Errorf("invalid resource item does not have ID on line %d", item.Assign.Line))
|
||||
continue
|
||||
}
|
||||
|
||||
key := item.Keys[0].Token.Value().(string)
|
||||
if key != "resource" {
|
||||
merr = multierror.Append(merr, fmt.Errorf("invalid key '%s' (line %d)", key, item.Assign.Line))
|
||||
continue
|
||||
}
|
||||
|
||||
resourceName := item.Keys[1].Token.Value().(string)
|
||||
_, ok := bindings[resourceName]
|
||||
if !ok {
|
||||
bindings[resourceName] = make(StringSet)
|
||||
}
|
||||
|
||||
resourceList := item.Val.(*ast.ObjectType).List
|
||||
for _, rolesItem := range resourceList.Items {
|
||||
key := rolesItem.Keys[0].Token.Text
|
||||
switch key {
|
||||
case "roles":
|
||||
parseRoles(rolesItem, bindings[resourceName], merr)
|
||||
default:
|
||||
merr = multierror.Append(merr, fmt.Errorf("invalid key '%s' in resource '%s' (line %d)", key, resourceName, item.Assign.Line))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
err := merr.ErrorOrNil()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bindings, nil
|
||||
}
|
||||
|
||||
func parseRoles(item *ast.ObjectItem, roleSet StringSet, merr *multierror.Error) {
|
||||
lst, ok := item.Val.(*ast.ListType)
|
||||
if !ok {
|
||||
merr = multierror.Append(merr, fmt.Errorf("roles must be a list (line %d)", item.Assign.Line))
|
||||
return
|
||||
}
|
||||
|
||||
for _, roleItem := range lst.List {
|
||||
role := roleItem.(*ast.LiteralType).Token.Value().(string)
|
||||
|
||||
tkns := strings.Split(role, "/")
|
||||
switch len(tkns) {
|
||||
case 2:
|
||||
// "roles/X"
|
||||
if tkns[0] == "roles" {
|
||||
roleSet.Add(role)
|
||||
continue
|
||||
}
|
||||
case 4:
|
||||
// "projects/X/roles/Y" or "organizations/X/roles/Y"
|
||||
if (tkns[0] == "projects" || tkns[0] == "organizations") && tkns[2] == "roles" {
|
||||
roleSet.Add(role)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
merr = multierror.Append(merr, fmt.Errorf("invalid role: %s (line %d): must be project-level, organization-level, or global role", role, roleItem.Pos().Line))
|
||||
}
|
||||
}
|
||||
80
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/string_set.go
generated
vendored
Normal file
80
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/string_set.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package util
|
||||
|
||||
// A set of strings
|
||||
type StringSet map[string]struct{}
|
||||
|
||||
func ToSet(values []string) StringSet {
|
||||
s := make(StringSet)
|
||||
for _, v := range values {
|
||||
s[v] = struct{}{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (ss StringSet) Add(v string) {
|
||||
ss[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (ss StringSet) ToSlice() []string {
|
||||
ls := make([]string, len(ss))
|
||||
i := 0
|
||||
for r := range ss {
|
||||
ls[i] = r
|
||||
i++
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
func (ss StringSet) Includes(v string) bool {
|
||||
_, ok := ss[v]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ss StringSet) Update(members ...string) {
|
||||
for _, v := range members {
|
||||
ss[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss StringSet) Union(other StringSet) StringSet {
|
||||
un := make(StringSet)
|
||||
for v := range ss {
|
||||
un[v] = struct{}{}
|
||||
}
|
||||
for v := range other {
|
||||
un[v] = struct{}{}
|
||||
}
|
||||
return un
|
||||
}
|
||||
|
||||
func (ss StringSet) Intersection(other StringSet) StringSet {
|
||||
inter := make(StringSet)
|
||||
|
||||
var s StringSet
|
||||
if len(ss) > len(other) {
|
||||
s = other
|
||||
} else {
|
||||
s = ss
|
||||
}
|
||||
|
||||
for v := range s {
|
||||
if other.Includes(v) {
|
||||
inter[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return inter
|
||||
}
|
||||
|
||||
func (ss StringSet) Sub(other StringSet) StringSet {
|
||||
sub := make(StringSet)
|
||||
for v := range ss {
|
||||
if !other.Includes(v) {
|
||||
sub[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func (ss StringSet) Equals(other StringSet) bool {
|
||||
return len(ss.Intersection(other)) == len(ss)
|
||||
}
|
||||
31
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/testing.go
generated
vendored
Normal file
31
vendor/github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util/testing.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/go-gcp-common/gcputil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const googleCredentialsEnv = "TEST_GOOGLE_CREDENTIALS"
|
||||
const googleProjectEnv = "TEST_GOOGLE_PROJECT"
|
||||
|
||||
func GetTestCredentials(t *testing.T) (string, *gcputil.GcpCredentials) {
|
||||
credentialsJSON := os.Getenv(googleCredentialsEnv)
|
||||
if credentialsJSON == "" {
|
||||
t.Fatalf("%s must be set to JSON string of valid Google credentials file", googleCredentialsEnv)
|
||||
}
|
||||
|
||||
credentials, err := gcputil.Credentials(credentialsJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("valid Google credentials JSON could not be read from %s env variable: %v", googleCredentialsEnv, err)
|
||||
}
|
||||
return credentialsJSON, credentials
|
||||
}
|
||||
|
||||
func GetTestProject(t *testing.T) string {
|
||||
project := os.Getenv(googleProjectEnv)
|
||||
if project == "" {
|
||||
t.Fatalf("%s must be set to JSON string of valid Google credentials file", googleProjectEnv)
|
||||
}
|
||||
return project
|
||||
}
|
||||
24
vendor/vendor.json
vendored
24
vendor/vendor.json
vendored
@@ -1080,6 +1080,12 @@
|
||||
"revision": "d5fe4b57a186c716b0e00b8c301cbd9b4182694d",
|
||||
"revisionTime": "2017-12-18T14:54:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "mxGkRISfv2EldeYihuciIYrKNjI=",
|
||||
"path": "github.com/hashicorp/go-gcp-common/gcputil",
|
||||
"revision": "4b38f46ac60aebc9c67f2548aec1ff5bc1e9a4eb",
|
||||
"revisionTime": "2018-02-23T19:15:17Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "xTdKLI1gAJOTqswvr15a/fI8ucA=",
|
||||
"path": "github.com/hashicorp/go-hclog",
|
||||
@@ -1302,6 +1308,24 @@
|
||||
"revision": "6c7dd3f219b06c4fa249bff2b859733a2f262e3b",
|
||||
"revisionTime": "2018-03-20T18:05:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "w+bhfXfIBMWNMuH2SJqw6Y5c6mk=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin",
|
||||
"revision": "7633b05ac6d9a8f77f9255ef5aea09f35a145b0b",
|
||||
"revisionTime": "2018-03-22T03:06:48Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0bNwTVNqwHHUJ+r1usgdtGsiSJs=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil",
|
||||
"revision": "69b9785fc3a7b4e7ca40c13f1d5b6284d43bf273",
|
||||
"revisionTime": "2018-03-21T19:18:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qF5wbamqBc44thWKDSmF8ayi6nI=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util",
|
||||
"revision": "69b9785fc3a7b4e7ca40c13f1d5b6284d43bf273",
|
||||
"revisionTime": "2018-03-21T19:18:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZYuIUFGjAZ2rgy/zwdjfANFZc/U=",
|
||||
"path": "github.com/hashicorp/vault-plugin-secrets-kv",
|
||||
|
||||
Reference in New Issue
Block a user