Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aa6343f3f |
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
# Copyright IBM Corp. 2024, 2026
|
# Copyright (c) HashiCorp, Inc.
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
## PCI review checklist
|
|
||||||
|
|
||||||
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
|
|
||||||
|
|
||||||
- [ ] I have documented a clear reason for, and description of, the change I am making.
|
|
||||||
|
|
||||||
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
|
|
||||||
|
|
||||||
- [ ] If applicable, I've documented the impact of any changes to security controls.
|
|
||||||
|
|
||||||
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright IBM Corp. 2024, 2026
|
Copyright (c) 2021 HashiCorp, Inc.
|
||||||
|
|
||||||
Mozilla Public License Version 2.0
|
Mozilla Public License Version 2.0
|
||||||
==================================
|
==================================
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
This repository is a community fork of hashicorp/boundary-plugin-gcp
|
|
||||||
(https://github.com/hashicorp/boundary-plugin-gcp), maintained for Threshold.
|
|
||||||
|
|
||||||
The original work is licensed under the Mozilla Public License 2.0 (see LICENSE).
|
|
||||||
Original copyright IBM Corp. / HashiCorp, Inc. is retained in all source file
|
|
||||||
headers and in LICENSE. Modifications are limited to the Go module path and the
|
|
||||||
import paths of the Boundary SDK/API packages.
|
|
||||||
|
|
||||||
This fork is not affiliated with, endorsed by, or supported by HashiCorp or IBM.
|
|
||||||
@@ -1,12 +1,3 @@
|
|||||||
## About this fork
|
|
||||||
|
|
||||||
This is a community fork of
|
|
||||||
[hashicorp/boundary-plugin-gcp](https://github.com/hashicorp/boundary-plugin-gcp)
|
|
||||||
for use with Threshold. It is licensed under the MPL-2.0 (see `LICENSE`), and
|
|
||||||
the original copyright notices are retained. It is not affiliated with,
|
|
||||||
endorsed by, or supported by HashiCorp or IBM. Changes from upstream are limited
|
|
||||||
to the Go module path and SDK import paths; see `NOTICE`.
|
|
||||||
|
|
||||||
# GCP Plugin for HashiCorp Boundary
|
# GCP Plugin for HashiCorp Boundary
|
||||||
|
|
||||||
This repo contains the GCP plugin for [HashiCorp
|
This repo contains the GCP plugin for [HashiCorp
|
||||||
|
|||||||
@@ -1,70 +1,62 @@
|
|||||||
module repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp
|
module github.com/hashicorp/boundary-plugin-gcp
|
||||||
|
|
||||||
go 1.26.9
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/iam v1.5.3
|
cloud.google.com/go/iam v1.2.0
|
||||||
cloud.google.com/go/resourcemanager v1.10.7
|
cloud.google.com/go/resourcemanager v1.10.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0
|
github.com/googleapis/gax-go/v2 v2.13.0
|
||||||
github.com/hashicorp/terraform-json v0.23.0
|
github.com/hashicorp/boundary/sdk v0.0.47
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.22.0
|
||||||
google.golang.org/api v0.293.0
|
google.golang.org/api v0.196.0
|
||||||
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.123.0 // indirect
|
cloud.google.com/go v0.115.1 // indirect
|
||||||
cloud.google.com/go/auth v0.23.1 // indirect
|
cloud.google.com/go/auth v0.9.3 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||||
cloud.google.com/go/longrunning v0.8.0 // indirect
|
cloud.google.com/go/longrunning v0.6.0 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.4 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21 // indirect
|
github.com/google/s2a-go v0.1.8 // indirect
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.15.0 // indirect
|
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.45.0 // indirect
|
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.45.0 // indirect
|
|
||||||
golang.org/x/sync v0.23.0 // indirect
|
|
||||||
golang.org/x/time v0.15.0 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.54.0
|
cloud.google.com/go/compute v1.28.0
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/eventlogger v0.2.11 // indirect
|
github.com/hashicorp/eventlogger v0.2.9 // indirect
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 // indirect
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mitchellh/pointerstructure v1.2.1 // indirect
|
github.com/mitchellh/pointerstructure v1.2.1 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/crypto v0.57.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/net v0.60.0 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/sys v0.48.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/text v0.42.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e
|
google.golang.org/grpc v1.66.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.34.2
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
@@ -1,128 +1,158 @@
|
|||||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||||
cloud.google.com/go/auth v0.23.1 h1:1tPpBPG02lQHmoiAvs9egyCASqXP0xgobptjZzov/Jg=
|
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||||
cloud.google.com/go/auth v0.23.1/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
|
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||||
cloud.google.com/go/compute v1.54.0 h1:4CKmnpO+40z44bKG5bdcKxQ7ocNpRtOc9SCLLUzze1w=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||||
cloud.google.com/go/compute v1.54.0/go.mod h1:RfBj0L1x/pIM84BrzNX2V21oEv16EKRPBiTcBRRH1Ww=
|
cloud.google.com/go/compute v1.28.0 h1:OPtBxMcheSS+DWfci803qvPly3d4w7Eu5ztKBcFfzwk=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
cloud.google.com/go/compute v1.28.0/go.mod h1:DEqZBtYrDnD5PvjsKwb3onnhX+qjdCVM7eshj1XdjV4=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8=
|
||||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q=
|
||||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI=
|
||||||
cloud.google.com/go/resourcemanager v1.10.7 h1:oPZKIdjyVTuag+D4HF7HO0mnSqcqgjcuA18xblwA0V0=
|
cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts=
|
||||||
cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs=
|
cloud.google.com/go/resourcemanager v1.10.1 h1:fO/QoSJ1lepmTM9dCbSXYWgTIhecmQkpY0mM1X9OGN0=
|
||||||
|
cloud.google.com/go/resourcemanager v1.10.1/go.mod h1:A/ANV/Sv7y7fcjd4LSH7PJGTZcWRkO/69yN5UhYUmvE=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
|
||||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane/envoy v1.39.0 h1:1uwRDYPYG8BIBU9Mj1sUAebNmlM6beu/ZKKweSLDxk8=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane/envoy v1.39.0/go.mod h1:5e4ylfTZO723MEEFsCpSW4ZEBWR8mwkEyXfwJBTCZ9c=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||||
|
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.3 h1:QRje2j5GZimBzlbhGA2V2QlGNgL8G6e+wGo/+/2bWI0=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
|
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
|
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||||
github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
|
github.com/hashicorp/boundary/sdk v0.0.47 h1:h5AXOASS2duHkCYEmNKnI9AR6YBZxD7VbFPV8BoE0z0=
|
||||||
github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
|
github.com/hashicorp/boundary/sdk v0.0.47/go.mod h1:9iOT7kDM6mYcSkKxNuZlv8rP7U5BG1kXoevjLLL8lNQ=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/eventlogger v0.2.11 h1:lkK8ARM/DrCeL5deH2yVS6p40337dESi+77p9pIfLqo=
|
github.com/hashicorp/eventlogger v0.2.9 h1:QO8hPXNngadMp72FVNTwIduLAG9fcLP7t59bSFd7gDY=
|
||||||
github.com/hashicorp/eventlogger v0.2.11/go.mod h1:Rmc3MEopz7jUnLokZVTWAsXaEP5rqd20ObGS+pcau3U=
|
github.com/hashicorp/eventlogger v0.2.9/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM=
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
|
||||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8 h1:/GIUjn9GkFXMk/8/irRdbdtmx8CcyeyWdVy/E5LvzyA=
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM=
|
||||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8/go.mod h1:JDc9UOD4EVRDIwPVethJcT5Ibi/Nas6eQDPtA60iwP0=
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg=
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 h1:afreZ1WJd0WI7v4NsMZ1aL7V/T59sxPuKmQDgGUja20=
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0=
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20/go.mod h1:NeK2Ul15t1zutp/dZzt28XQrGZHosbxE/QLNNfaWObM=
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y=
|
||||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4=
|
||||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13/go.mod h1:NDRQ/F3DXTylqjORAP0cA+puH/JFrLlT+NlqxHK2/e8=
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8=
|
||||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10 h1:2iDz+t0JLl1W0tJhvmhsh/UBgT1JgC8Qxz8HxYMWXQo=
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY=
|
||||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10/go.mod h1:eZkXE+osawMrAWR4wJRmyKauUwH6mNGbjFuiDujnbPk=
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M=
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc=
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
|
||||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8 h1:pgSicufBI+MvaIG9Keykb3k9B3sWtDNjmsoyrl2/8Qw=
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY=
|
||||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8/go.mod h1:sBcjk+paCXCMR9HHLcrYSfPz2FsskD8MQuotKiyM2aA=
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg=
|
||||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
|
||||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
|
||||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
|
||||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
|
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
|
||||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
|
||||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
|
||||||
|
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
@@ -132,74 +162,146 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||||
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||||
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
|
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||||
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
|
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
google.golang.org/api v0.293.0 h1:p9XIWOf63U4OgYx120ZwVU8+vl4XTPmWfgVPnmOAS9w=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
google.golang.org/api v0.293.0/go.mod h1:6n5tjEB1gzwniZTepZ0g5u+wM7Bof5GeULCx/zh8ZE0=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 h1:lrupDmKL3p5kEX1M92oan027eCKcouzjuPbH6YBK+Rs=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e h1:ar+feAPij1Znzug45m6AoShUCc5AaR8FLA3I6LZd94k=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e/go.mod h1:7aY/KlAHanMAVIWHnk8wloQsdlfwzDz/PP9vCbJG6CU=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||||
|
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.196.0 h1:k/RafYqebaIJBO3+SMnfEGtFVlvp5vSgqTUF54UN/zg=
|
||||||
|
google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
|
||||||
|
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||||
|
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0 h1:ExuSGqE1pA/1DnS5xrSK7kEhD+4g5hIJC5TgmwH5LAU=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0/go.mod h1:cWxYF3tEkYEKTY76ub8kL+vdGmopO0YfJqJGGQJov7k=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -6,8 +6,8 @@ package credential
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/errors"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/errors"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/values"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/values"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,16 +115,13 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes)
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// service account impersonation authentication requires the target service account id and client_email
|
// dynamic credentials requires the target service account id, cl and private key
|
||||||
case attrs.TargetServiceAccountId != "" && attrs.ClientEmail == "":
|
case attrs.TargetServiceAccountId != "" && (privateKey == "" || attrs.ClientEmail == ""):
|
||||||
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when target service account id is set"
|
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when target service account id is set"
|
||||||
// service account impersonation authentication requires the target service account id and private key
|
|
||||||
case attrs.TargetServiceAccountId != "" && privateKey == "":
|
|
||||||
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when target service account id is set"
|
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when target service account id is set"
|
||||||
// service account key credentials requires the private key and client email
|
// static credentials requires the private key and client email
|
||||||
case privateKey != "" && attrs.ClientEmail == "":
|
case privateKey != "" && attrs.ClientEmail == "":
|
||||||
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when private key is set"
|
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when private key is set"
|
||||||
// service account key credentials requires the client_email
|
|
||||||
case attrs.ClientEmail != "" && privateKey == "":
|
case attrs.ClientEmail != "" && privateKey == "":
|
||||||
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when client email is set"
|
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when client email is set"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -121,7 +121,6 @@ func TestGetCredentialsConfig(t *testing.T) {
|
|||||||
expected: &Config{
|
expected: &Config{
|
||||||
Zone: "us-central-1a",
|
Zone: "us-central-1a",
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -129,7 +128,6 @@ func TestGetCredentialsConfig(t *testing.T) {
|
|||||||
attrs: nil,
|
attrs: nil,
|
||||||
expected: &Config{
|
expected: &Config{
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -162,7 +160,6 @@ func TestGetCredentialsConfig(t *testing.T) {
|
|||||||
PrivateKey: "test-private-key",
|
PrivateKey: "test-private-key",
|
||||||
PrivateKeyId: "test-private-key-id",
|
PrivateKeyId: "test-private-key-id",
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -229,7 +226,6 @@ func TestGetCredentialsConfig(t *testing.T) {
|
|||||||
PrivateKeyId: "test-private-key-id",
|
PrivateKeyId: "test-private-key-id",
|
||||||
ClientEmail: "test@test.com",
|
ClientEmail: "test@test.com",
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,7 +248,6 @@ func TestGetCredentialsConfig(t *testing.T) {
|
|||||||
ClientEmail: "test@test.com",
|
ClientEmail: "test@test.com",
|
||||||
TargetServiceAccountId: "test-target-service-account",
|
TargetServiceAccountId: "test-target-service-account",
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -41,9 +41,6 @@ const (
|
|||||||
// and then controlling the service account's access by granting it IAM roles.
|
// and then controlling the service account's access by granting it IAM roles.
|
||||||
// https://cloud.google.com/compute/docs/access/service-accounts#scopes_best_practice
|
// https://cloud.google.com/compute/docs/access/service-accounts#scopes_best_practice
|
||||||
defaultGCPScope = "https://www.googleapis.com/auth/cloud-platform"
|
defaultGCPScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||||
// defaultValidateServiceAccountKeyTimeout is the timeout for validating
|
|
||||||
// a service account key.
|
|
||||||
defaultValidateServiceAccountKeyTimeout = 5 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the configuration for the GCP credential.
|
// Config is the configuration for the GCP credential.
|
||||||
@@ -55,7 +52,6 @@ type Config struct {
|
|||||||
ClientEmail string
|
ClientEmail string
|
||||||
TargetServiceAccountId string
|
TargetServiceAccountId string
|
||||||
Scopes []string
|
Scopes []string
|
||||||
validateServiceAccountKeyTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// credentials represents a simplified version of the GCP credentials file format.
|
// credentials represents a simplified version of the GCP credentials file format.
|
||||||
@@ -83,7 +79,6 @@ func NewConfig(opt ...Option) (*Config, error) {
|
|||||||
TargetServiceAccountId: opts.WithTargetServiceAccountId,
|
TargetServiceAccountId: opts.WithTargetServiceAccountId,
|
||||||
Zone: opts.WithZone,
|
Zone: opts.WithZone,
|
||||||
Scopes: opts.WithScopes,
|
Scopes: opts.WithScopes,
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@@ -103,13 +98,11 @@ func (c *Config) toCredentials() *credentials {
|
|||||||
func (c *Config) clone() *Config {
|
func (c *Config) clone() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
ProjectId: c.ProjectId,
|
ProjectId: c.ProjectId,
|
||||||
Zone: c.Zone,
|
|
||||||
PrivateKey: c.PrivateKey,
|
PrivateKey: c.PrivateKey,
|
||||||
PrivateKeyId: c.PrivateKeyId,
|
PrivateKeyId: c.PrivateKeyId,
|
||||||
ClientEmail: c.ClientEmail,
|
ClientEmail: c.ClientEmail,
|
||||||
TargetServiceAccountId: c.TargetServiceAccountId,
|
TargetServiceAccountId: c.TargetServiceAccountId,
|
||||||
Scopes: c.Scopes,
|
Scopes: c.Scopes,
|
||||||
validateServiceAccountKeyTimeout: c.validateServiceAccountKeyTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -410,7 +410,6 @@ func TestNewConfig(t *testing.T) {
|
|||||||
TargetServiceAccountId: "target-service-account@example.com",
|
TargetServiceAccountId: "target-service-account@example.com",
|
||||||
Zone: "test-zone",
|
Zone: "test-zone",
|
||||||
Scopes: []string{"scope1", "scope2"},
|
Scopes: []string{"scope1", "scope2"},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
@@ -419,7 +418,6 @@ func TestNewConfig(t *testing.T) {
|
|||||||
options: []Option{},
|
options: []Option{},
|
||||||
expected: &Config{
|
expected: &Config{
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
@@ -451,7 +449,6 @@ func TestNewConfig(t *testing.T) {
|
|||||||
TargetServiceAccountId: "target-service-account@example.com",
|
TargetServiceAccountId: "target-service-account@example.com",
|
||||||
Zone: "test-zone",
|
Zone: "test-zone",
|
||||||
Scopes: []string{defaultGCPScope},
|
Scopes: []string{defaultGCPScope},
|
||||||
validateServiceAccountKeyTimeout: defaultValidateServiceAccountKeyTimeout,
|
|
||||||
},
|
},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|||||||
+40
-113
@@ -1,15 +1,13 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"strings"
|
||||||
|
|
||||||
admin "cloud.google.com/go/iam/admin/apiv1"
|
admin "cloud.google.com/go/iam/admin/apiv1"
|
||||||
"cloud.google.com/go/iam/admin/apiv1/adminpb"
|
"cloud.google.com/go/iam/admin/apiv1/adminpb"
|
||||||
@@ -21,54 +19,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ComputeInstancesListPermission is the IAM permission required
|
|
||||||
// to list compute instances.
|
|
||||||
ComputeInstancesListPermission = "compute.instances.list"
|
ComputeInstancesListPermission = "compute.instances.list"
|
||||||
// IAMServiceAccountKeysCreatePermission is the IAM permission
|
|
||||||
// required to create a service account key.
|
|
||||||
// This permission is required to rotate service account keys.
|
|
||||||
IAMServiceAccountKeysCreatePermission = "iam.serviceAccountKeys.create"
|
IAMServiceAccountKeysCreatePermission = "iam.serviceAccountKeys.create"
|
||||||
// IAMServiceAccountKeysDeletePermission is the IAM permission
|
|
||||||
// required to delete a service account key.
|
|
||||||
// This permission is required to rotate service account keys.
|
|
||||||
IAMServiceAccountKeysDeletePermission = "iam.serviceAccountKeys.delete"
|
IAMServiceAccountKeysDeletePermission = "iam.serviceAccountKeys.delete"
|
||||||
|
CreateServiceAccountKeyResourceName = "projects/-/serviceAccounts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceAccountPrivateKey represents a decoded PrivateKeyData
|
|
||||||
// from a Service Account Key.
|
|
||||||
// https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts.keys#ServiceAccountKey
|
|
||||||
type ServiceAccountPrivateKey struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
ProjectID string `json:"project_id"`
|
|
||||||
PrivateKeyID string `json:"private_key_id"`
|
|
||||||
PrivateKey string `json:"private_key"`
|
|
||||||
ClientEmail string `json:"client_email"`
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
AuthURI string `json:"auth_uri"`
|
|
||||||
TokenURI string `json:"token_uri"`
|
|
||||||
AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
|
|
||||||
ClientX509CertURL string `json:"client_x509_cert_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateCredsCallback is called to validate the credentials after rotating the service account key.
|
|
||||||
// The callback should return an error if the credentials are invalid.
|
|
||||||
type ValidateCredsCallback func(*Config, ...option.ClientOption) error
|
|
||||||
|
|
||||||
// RotateServiceAccountKey takes the private key from this credentials config
|
// RotateServiceAccountKey takes the private key from this credentials config
|
||||||
// and first creates a new private key and private key id, then deletes the
|
// and first creates a new private key and private key id, then deletes the
|
||||||
// old private key.
|
// old private key.
|
||||||
//
|
//
|
||||||
// If deletion of the old private key is successful, the new private key and
|
// If deletion of the old private key is successful, the new private key and
|
||||||
// private key id are written into the credentials config and nil is returned.
|
// private key id are written into the credentials config and nil is returned.
|
||||||
// If validation fails or deleting the old private key fails, the function
|
|
||||||
// attempts to delete the newly created key as a rollback operation.
|
|
||||||
// On any error, the old credentials are not overwritten.
|
// On any error, the old credentials are not overwritten.
|
||||||
func (c *Config) RotateServiceAccountKey(
|
func (c *Config) RotateServiceAccountKey(ctx context.Context, permissions []string, opts ...option.ClientOption) error {
|
||||||
ctx context.Context,
|
|
||||||
permissions []string,
|
|
||||||
validateCredsCallback ValidateCredsCallback,
|
|
||||||
opts ...option.ClientOption,
|
|
||||||
) error {
|
|
||||||
if c.PrivateKey == "" {
|
if c.PrivateKey == "" {
|
||||||
return status.Error(codes.InvalidArgument, "cannot rotate credentials when private key is not set")
|
return status.Error(codes.InvalidArgument, "cannot rotate credentials when private key is not set")
|
||||||
}
|
}
|
||||||
@@ -90,13 +54,13 @@ func (c *Config) RotateServiceAccountKey(
|
|||||||
clientOptions := []option.ClientOption{option.WithTokenSource(creds.TokenSource)}
|
clientOptions := []option.ClientOption{option.WithTokenSource(creds.TokenSource)}
|
||||||
clientOptions = append(clientOptions, opts...)
|
clientOptions = append(clientOptions, opts...)
|
||||||
|
|
||||||
currentIAMClient, err := admin.NewIamClient(ctx, clientOptions...)
|
iamClient, err := admin.NewIamClient(ctx, clientOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Internal, "error creating IAM client: %v", err)
|
return status.Errorf(codes.Internal, "error creating IAM client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
createServiceAccountKeyRes, err := currentIAMClient.CreateServiceAccountKey(ctx, &adminpb.CreateServiceAccountKeyRequest{
|
createServiceAccountKeyRes, err := iamClient.CreateServiceAccountKey(ctx, &adminpb.CreateServiceAccountKeyRequest{
|
||||||
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s", c.ProjectId, c.ClientEmail),
|
Name: fmt.Sprintf("%s/%s", CreateServiceAccountKeyResourceName, c.ClientEmail),
|
||||||
PrivateKeyType: adminpb.ServiceAccountPrivateKeyType_TYPE_GOOGLE_CREDENTIALS_FILE,
|
PrivateKeyType: adminpb.ServiceAccountPrivateKeyType_TYPE_GOOGLE_CREDENTIALS_FILE,
|
||||||
KeyAlgorithm: adminpb.ServiceAccountKeyAlgorithm_KEY_ALG_RSA_2048,
|
KeyAlgorithm: adminpb.ServiceAccountKeyAlgorithm_KEY_ALG_RSA_2048,
|
||||||
})
|
})
|
||||||
@@ -104,16 +68,15 @@ func (c *Config) RotateServiceAccountKey(
|
|||||||
return status.Errorf(codes.Internal, "error creating service account key: %v", err)
|
return status.Errorf(codes.Internal, "error creating service account key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceAccountKey ServiceAccountPrivateKey
|
privateKeyId, err := getPrivateKeyIdFromName(createServiceAccountKeyRes.Name)
|
||||||
err = json.Unmarshal(createServiceAccountKeyRes.PrivateKeyData, &serviceAccountKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Internal, "error unmarshalling service account key: %v", err)
|
return status.Errorf(codes.Internal, "error parsing private key ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the config, update the private key and private key ID with the new key.
|
// Clone the config, update the private key and private key ID with the new key.
|
||||||
newConfig := c.clone()
|
newConfig := c.clone()
|
||||||
newConfig.PrivateKey = serviceAccountKey.PrivateKey
|
newConfig.PrivateKey = string(createServiceAccountKeyRes.PrivateKeyData)
|
||||||
newConfig.PrivateKeyId = serviceAccountKey.PrivateKeyID
|
newConfig.PrivateKeyId = privateKeyId
|
||||||
|
|
||||||
newCreds, err := newConfig.GenerateCredentials(ctx)
|
newCreds, err := newConfig.GenerateCredentials(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -125,40 +88,22 @@ func (c *Config) RotateServiceAccountKey(
|
|||||||
clientOptions = []option.ClientOption{option.WithTokenSource(newCreds.TokenSource)}
|
clientOptions = []option.ClientOption{option.WithTokenSource(newCreds.TokenSource)}
|
||||||
clientOptions = append(clientOptions, opts...)
|
clientOptions = append(clientOptions, opts...)
|
||||||
|
|
||||||
// Validate the new service account key
|
// Validate that the new credentials have the necessary permissions.
|
||||||
err = newConfig.ValidateServiceAccountKey(ctx, permissions, validateCredsCallback, clientOptions...)
|
_, err = newConfig.ValidateIamPermissions(ctx, permissions, clientOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Roll back the new key if validation fails.
|
return status.Errorf(codes.PermissionDenied, "error testing IAM permissions with rotated service account key: %v", err)
|
||||||
rollbackErr := currentIAMClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
|
||||||
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s/keys/%s", newConfig.ProjectId, newConfig.ClientEmail, newConfig.PrivateKeyId),
|
|
||||||
})
|
|
||||||
if rollbackErr != nil {
|
|
||||||
return status.Errorf(codes.Internal, "error validating rotated service account key: %v; error rolling back newly created service account key: %v; service account may have multiple active keys", err, rollbackErr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.Errorf(codes.PermissionDenied, "error validating rotated service account key: %v; successfully rolled back newly created service account key", err)
|
iamClient, err = admin.NewIamClient(ctx, clientOptions...)
|
||||||
}
|
|
||||||
|
|
||||||
newIAMClient, err := admin.NewIamClient(ctx, clientOptions...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Internal, "error creating IAM client with rotated service account key: %v", err)
|
return status.Errorf(codes.Internal, "error creating IAM client with rotated service account key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = newIAMClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
err = iamClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
||||||
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s/keys/%s", c.ProjectId, c.ClientEmail, c.PrivateKeyId),
|
Name: fmt.Sprintf("%s/%s/keys/%s", CreateServiceAccountKeyResourceName, c.ClientEmail, c.PrivateKeyId),
|
||||||
})
|
})
|
||||||
// If deletion of the old key fails, attempt to delete the new key to avoid leaving a dangling key, then return an error.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// delete the new key using old client since the new client may not have permissions that the old client has.
|
return status.Errorf(codes.Internal, "error deleting service account key: %v", err)
|
||||||
rollbackErr := currentIAMClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
|
||||||
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s/keys/%s", newConfig.ProjectId, newConfig.ClientEmail, newConfig.PrivateKeyId),
|
|
||||||
})
|
|
||||||
// If rollback also fails, return an error indicating both the failure to delete the old key and the failure to roll back the new key.
|
|
||||||
if rollbackErr != nil {
|
|
||||||
return status.Errorf(codes.Internal, "error deleting service account key: %v; error rolling back newly created service account key: %v; service account may have multiple active keys", err, rollbackErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return status.Errorf(codes.Internal, "error deleting service account key: %v; successfully rolled back newly created service account key", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.PrivateKey = newConfig.PrivateKey
|
c.PrivateKey = newConfig.PrivateKey
|
||||||
@@ -167,44 +112,6 @@ func (c *Config) RotateServiceAccountKey(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateServiceAccountKey validates the service account key by checking the IAM permissions
|
|
||||||
// and calling the validation callback.
|
|
||||||
// The function will retry validation until the timeout is reached.
|
|
||||||
func (c *Config) ValidateServiceAccountKey(
|
|
||||||
ctx context.Context,
|
|
||||||
permissions []string,
|
|
||||||
validateCredsCallback ValidateCredsCallback,
|
|
||||||
opts ...option.ClientOption) error {
|
|
||||||
var validatePermissionsErr error
|
|
||||||
var validationCallbackErr error
|
|
||||||
|
|
||||||
ticker := time.NewTicker(200 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.validateServiceAccountKeyTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
return fmt.Errorf("failed to validate service account key before timeout: %w", errors.Join(validatePermissionsErr, validationCallbackErr))
|
|
||||||
}
|
|
||||||
return ctx.Err()
|
|
||||||
case <-ticker.C:
|
|
||||||
_, validatePermissionsErr = c.ValidateIamPermissions(ctx, permissions, opts...)
|
|
||||||
|
|
||||||
if validateCredsCallback != nil {
|
|
||||||
validationCallbackErr = validateCredsCallback(c, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if validatePermissionsErr == nil && validationCallbackErr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) DeletePrivateKey(ctx context.Context, opts ...option.ClientOption) error {
|
func (c *Config) DeletePrivateKey(ctx context.Context, opts ...option.ClientOption) error {
|
||||||
if c.PrivateKey == "" {
|
if c.PrivateKey == "" {
|
||||||
return status.Error(codes.InvalidArgument, "cannot delete credentials when private key is not set")
|
return status.Error(codes.InvalidArgument, "cannot delete credentials when private key is not set")
|
||||||
@@ -233,7 +140,7 @@ func (c *Config) DeletePrivateKey(ctx context.Context, opts ...option.ClientOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = iamClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
err = iamClient.DeleteServiceAccountKey(ctx, &adminpb.DeleteServiceAccountKeyRequest{
|
||||||
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s/keys/%s", c.ProjectId, c.ClientEmail, c.PrivateKeyId),
|
Name: fmt.Sprintf("projects/-/serviceAccounts/%s/keys/%s", c.ClientEmail, c.PrivateKeyId),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Unknown, "error deleting service account key: %v", err)
|
return status.Errorf(codes.Unknown, "error deleting service account key: %v", err)
|
||||||
@@ -249,7 +156,18 @@ func (c *Config) ValidateIamPermissions(ctx context.Context, permissions []strin
|
|||||||
return nil, status.Error(codes.InvalidArgument, "permissions are required")
|
return nil, status.Error(codes.InvalidArgument, "permissions are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
rmClient, err := resourcemanager.NewProjectsClient(ctx, opts...)
|
creds, err := c.GenerateCredentials(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "error generating credentials: %v", err)
|
||||||
|
}
|
||||||
|
if creds.TokenSource == nil {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "error generating credentials: token source is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOptions := []option.ClientOption{option.WithTokenSource(creds.TokenSource)}
|
||||||
|
clientOptions = append(clientOptions, opts...)
|
||||||
|
|
||||||
|
rmClient, err := resourcemanager.NewProjectsClient(ctx, clientOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to create Resource Manager client: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to create Resource Manager client: %v", err)
|
||||||
}
|
}
|
||||||
@@ -279,3 +197,12 @@ func (c *Config) ValidateIamPermissions(ctx context.Context, permissions []strin
|
|||||||
|
|
||||||
return resp.Permissions, nil
|
return resp.Permissions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPrivateKeyIdFromName extracts the private key ID from the name of a service account key.
|
||||||
|
func getPrivateKeyIdFromName(input string) (string, error) {
|
||||||
|
lastSlashIndex := strings.LastIndex(input, "/")
|
||||||
|
if lastSlashIndex != -1 {
|
||||||
|
return input[lastSlashIndex+1:], nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("could not find private key ID in %s", input)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"cloud.google.com/go/iam/admin/apiv1/adminpb"
|
"cloud.google.com/go/iam/admin/apiv1/adminpb"
|
||||||
"cloud.google.com/go/iam/apiv1/iampb"
|
"cloud.google.com/go/iam/apiv1/iampb"
|
||||||
@@ -31,7 +30,6 @@ func TestRotateServiceAccountKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testValidateTimeout := 1 * time.Second
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -72,81 +70,21 @@ func TestRotateServiceAccountKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
setup: func() {
|
setup: func() {
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = errors.New("create key error")
|
testIAMAdminServer.testCreateServiceAccountKeyError = errors.New("create key error")
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = nil
|
|
||||||
testResourceServer.testIamPermissionsError = nil
|
|
||||||
},
|
},
|
||||||
expectedError: errors.New("create key error"),
|
expectedError: errors.New("create key error"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ValidateServiceAccountKey fails and rollback succeeds",
|
name: "DeleteServiceAccountKey returns error",
|
||||||
config: &Config{
|
config: &Config{
|
||||||
PrivateKey: "some-private-key",
|
PrivateKey: "some-private-key",
|
||||||
PrivateKeyId: "some-private-key-id",
|
PrivateKeyId: "some-private-key-id",
|
||||||
ClientEmail: "client@example.com",
|
ClientEmail: "client@example.com",
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
},
|
||||||
setup: func() {
|
setup: func() {
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
testIAMAdminServer.testDeleteServiceAccountKeyError = errors.New("delete key error")
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = []error{nil}
|
|
||||||
testResourceServer.testIamPermissionsError = errors.New("iam permissions error")
|
|
||||||
},
|
},
|
||||||
expectedError: errors.New("successfully rolled back newly created service account key"),
|
expectedError: errors.New("delete key error"),
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ValidateServiceAccountKey fails and rollback fails",
|
|
||||||
config: &Config{
|
|
||||||
PrivateKey: "some-private-key",
|
|
||||||
PrivateKeyId: "some-private-key-id",
|
|
||||||
ClientEmail: "client@example.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
setup: func() {
|
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = []error{errors.New("rollback key error")}
|
|
||||||
testResourceServer.testIamPermissionsError = errors.New("iam permissions error")
|
|
||||||
},
|
|
||||||
expectedError: errors.New("error rolling back newly created service account key"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DeleteServiceAccountKey old key fails and rollback fails",
|
|
||||||
config: &Config{
|
|
||||||
PrivateKey: "some-private-key",
|
|
||||||
PrivateKeyId: "some-private-key-id",
|
|
||||||
ClientEmail: "client@example.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
setup: func() {
|
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = []error{
|
|
||||||
errors.New("delete key error"),
|
|
||||||
errors.New("rollback key error"),
|
|
||||||
}
|
|
||||||
testResourceServer.testIamPermissionsError = nil
|
|
||||||
},
|
|
||||||
expectedError: errors.New("error rolling back newly created service account key"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DeleteServiceAccountKey old key fails and rollback succeeds",
|
|
||||||
config: &Config{
|
|
||||||
PrivateKey: "some-private-key",
|
|
||||||
PrivateKeyId: "some-private-key-id",
|
|
||||||
ClientEmail: "client@example.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
setup: func() {
|
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = []error{
|
|
||||||
errors.New("delete key error"),
|
|
||||||
nil,
|
|
||||||
}
|
|
||||||
testResourceServer.testIamPermissionsError = nil
|
|
||||||
},
|
|
||||||
expectedError: errors.New("successfully rolled back newly created service account key"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Successful rotation",
|
name: "Successful rotation",
|
||||||
@@ -154,13 +92,10 @@ func TestRotateServiceAccountKey(t *testing.T) {
|
|||||||
PrivateKey: "some-private-key",
|
PrivateKey: "some-private-key",
|
||||||
PrivateKeyId: "some-private-key-id",
|
PrivateKeyId: "some-private-key-id",
|
||||||
ClientEmail: "client@example.com",
|
ClientEmail: "client@example.com",
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
},
|
||||||
setup: func() {
|
setup: func() {
|
||||||
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
testIAMAdminServer.testCreateServiceAccountKeyError = nil
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
testIAMAdminServer.testDeleteServiceAccountKeyError = nil
|
||||||
testIAMAdminServer.testDeleteServiceAccountKeyErrors = nil
|
|
||||||
testResourceServer.testIamPermissionsError = nil
|
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -193,14 +128,8 @@ func TestRotateServiceAccountKey(t *testing.T) {
|
|||||||
IAMServiceAccountKeysCreatePermission,
|
IAMServiceAccountKeysCreatePermission,
|
||||||
IAMServiceAccountKeysDeletePermission,
|
IAMServiceAccountKeysDeletePermission,
|
||||||
}
|
}
|
||||||
validateCredsCallbackCalled := false
|
|
||||||
|
|
||||||
validateCredsCallBack := func(c *Config, opts ...googleOption.ClientOption) error {
|
err = tt.config.RotateServiceAccountKey(ctx, permissions, testOptions...)
|
||||||
validateCredsCallbackCalled = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tt.config.RotateServiceAccountKey(ctx, permissions, validateCredsCallBack, testOptions...)
|
|
||||||
if tt.expectedError != nil {
|
if tt.expectedError != nil {
|
||||||
require.ErrorContains(t, err, tt.expectedError.Error())
|
require.ErrorContains(t, err, tt.expectedError.Error())
|
||||||
require.Equal(t, initialClientEmail, tt.config.ClientEmail)
|
require.Equal(t, initialClientEmail, tt.config.ClientEmail)
|
||||||
@@ -214,7 +143,6 @@ func TestRotateServiceAccountKey(t *testing.T) {
|
|||||||
require.NotEmpty(t, tt.config.PrivateKeyId)
|
require.NotEmpty(t, tt.config.PrivateKeyId)
|
||||||
require.NotEqual(t, initialPrivateKey, tt.config.PrivateKey)
|
require.NotEqual(t, initialPrivateKey, tt.config.PrivateKey)
|
||||||
require.NotEqual(t, initialPrivateKeyId, tt.config.PrivateKeyId)
|
require.NotEqual(t, initialPrivateKeyId, tt.config.PrivateKeyId)
|
||||||
require.True(t, validateCredsCallbackCalled)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +183,16 @@ func TestValidateIamPermissions(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
expectedErr: "permissions are required",
|
expectedErr: "permissions are required",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Failed to Generate Credentials",
|
||||||
|
config: &Config{
|
||||||
|
ProjectId: "test-project-id",
|
||||||
|
PrivateKey: "test-private-key",
|
||||||
|
},
|
||||||
|
permissions: []string{"permission1", "permission2"},
|
||||||
|
wantErr: true,
|
||||||
|
expectedErr: "failed to generate credentials",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Failed to Test IAM Permissions",
|
name: "Failed to Test IAM Permissions",
|
||||||
config: &Config{
|
config: &Config{
|
||||||
@@ -326,104 +264,6 @@ func TestValidateIamPermissions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateServiceAccountKey(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
testResourceServer := &testResourceServer{}
|
|
||||||
testValidateTimeout := 1 * time.Second
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
config *Config
|
|
||||||
permissions []string
|
|
||||||
validateCredsCallback ValidateCredsCallback
|
|
||||||
testIamPermissionsError error
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Successful validation",
|
|
||||||
config: &Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
PrivateKey: "test-private-key",
|
|
||||||
ClientEmail: "test@test.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
permissions: []string{
|
|
||||||
ComputeInstancesListPermission,
|
|
||||||
IAMServiceAccountKeysCreatePermission,
|
|
||||||
IAMServiceAccountKeysDeletePermission,
|
|
||||||
},
|
|
||||||
validateCredsCallback: func(c *Config, opts ...googleOption.ClientOption) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Validation callback returns error",
|
|
||||||
config: &Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
PrivateKey: "test-private-key",
|
|
||||||
ClientEmail: "test@test.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
permissions: []string{
|
|
||||||
ComputeInstancesListPermission,
|
|
||||||
IAMServiceAccountKeysCreatePermission,
|
|
||||||
IAMServiceAccountKeysDeletePermission,
|
|
||||||
},
|
|
||||||
validateCredsCallback: func(c *Config, opts ...googleOption.ClientOption) error {
|
|
||||||
return errors.New("validation callback error")
|
|
||||||
},
|
|
||||||
expectedError: errors.New("validation callback error"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IAM permissions validation returns error",
|
|
||||||
config: &Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
PrivateKey: "test-private-key",
|
|
||||||
ClientEmail: "test@test.com",
|
|
||||||
validateServiceAccountKeyTimeout: testValidateTimeout,
|
|
||||||
},
|
|
||||||
permissions: []string{
|
|
||||||
ComputeInstancesListPermission,
|
|
||||||
IAMServiceAccountKeysCreatePermission,
|
|
||||||
IAMServiceAccountKeysDeletePermission,
|
|
||||||
},
|
|
||||||
testIamPermissionsError: errors.New("IAM permissions error"),
|
|
||||||
expectedError: errors.New("failed to validate IAM permissions"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
testResourceServer.testIamPermissionsResponse = &iampb.TestIamPermissionsResponse{
|
|
||||||
Permissions: tt.permissions,
|
|
||||||
}
|
|
||||||
testResourceServer.testIamPermissionsError = tt.testIamPermissionsError
|
|
||||||
|
|
||||||
gsrv := NewGRPCServer()
|
|
||||||
resourcemanagerpb.RegisterProjectsServer(gsrv.Server, testResourceServer)
|
|
||||||
addr, err := gsrv.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testOptions := []googleOption.ClientOption{
|
|
||||||
googleOption.WithEndpoint(addr),
|
|
||||||
googleOption.WithoutAuthentication(),
|
|
||||||
googleOption.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
|
||||||
googleOption.WithTokenSource(nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tt.config.ValidateServiceAccountKey(ctx, tt.permissions, tt.validateCredsCallback, testOptions...)
|
|
||||||
if tt.expectedError != nil {
|
|
||||||
require.ErrorContains(t, err, tt.expectedError.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeletePrivateKey(t *testing.T) {
|
func TestDeletePrivateKey(t *testing.T) {
|
||||||
testIAMAdminServer := &testIAMAdminServer{}
|
testIAMAdminServer := &testIAMAdminServer{}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/values"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/values"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -37,28 +37,6 @@ func NewPersistedState(opt ...Option) (*PersistedState, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RotateCreds rotates the credentials for the GCP catalog and updates the last rotated time.
|
|
||||||
func (s *PersistedState) RotateCreds(
|
|
||||||
ctx context.Context,
|
|
||||||
permissions []string,
|
|
||||||
validateCredsCallback ValidateCredsCallback,
|
|
||||||
opts ...option.ClientOption) error {
|
|
||||||
if s.CredentialsConfig == nil {
|
|
||||||
return status.Error(codes.InvalidArgument, "missing credentials config")
|
|
||||||
}
|
|
||||||
if !s.CredentialsConfig.IsRotatable() {
|
|
||||||
return status.Error(codes.InvalidArgument, "cannot rotate non-rotatable credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.CredentialsConfig.RotateServiceAccountKey(ctx, permissions, validateCredsCallback, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return status.Errorf(codes.Internal, "failed to rotate credentials: %v", err)
|
|
||||||
}
|
|
||||||
s.CredsLastRotatedTime = time.Now()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceCreds replaces the private key in the state with a new key.
|
// ReplaceCreds replaces the private key in the state with a new key.
|
||||||
// If the existing key was rotated at any point in time, it is
|
// If the existing key was rotated at any point in time, it is
|
||||||
// deleted first, otherwise it's left alone. This method returns a
|
// deleted first, otherwise it's left alone. This method returns a
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package credential
|
package credential
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ type testIAMAdminServer struct {
|
|||||||
adminpb.UnimplementedIAMServer
|
adminpb.UnimplementedIAMServer
|
||||||
testCreateServiceAccountKeyError error
|
testCreateServiceAccountKeyError error
|
||||||
testDeleteServiceAccountKeyError error
|
testDeleteServiceAccountKeyError error
|
||||||
testDeleteServiceAccountKeyErrors []error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestIAMAdminServer(createServiceAccountKeyError error, deleteServiceAccountKeyError error) *testIAMAdminServer {
|
func NewTestIAMAdminServer(createServiceAccountKeyError error, deleteServiceAccountKeyError error) *testIAMAdminServer {
|
||||||
@@ -31,29 +29,14 @@ func NewTestIAMAdminServer(createServiceAccountKeyError error, deleteServiceAcco
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *testIAMAdminServer) CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest) (*adminpb.ServiceAccountKey, error) {
|
func (f *testIAMAdminServer) CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest) (*adminpb.ServiceAccountKey, error) {
|
||||||
serviceAccountKey := ServiceAccountPrivateKey{
|
|
||||||
PrivateKey: "updated-private-key",
|
|
||||||
PrivateKeyID: "updated-private-key-id",
|
|
||||||
}
|
|
||||||
serviceAccountKeyData, err := json.Marshal(serviceAccountKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal service account key: %v", err)
|
|
||||||
}
|
|
||||||
resp := &adminpb.ServiceAccountKey{
|
resp := &adminpb.ServiceAccountKey{
|
||||||
Name: "projects/-/serviceAccounts/1234/keys/updated-private-key-id",
|
Name: "projects/-/serviceAccounts/1234/keys/updated-private-key-id",
|
||||||
PrivateKeyData: serviceAccountKeyData,
|
PrivateKeyData: []byte("updated-private-key"),
|
||||||
}
|
}
|
||||||
return resp, f.testCreateServiceAccountKeyError
|
return resp, f.testCreateServiceAccountKeyError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testIAMAdminServer) DeleteServiceAccountKey(ctx context.Context, req *adminpb.DeleteServiceAccountKeyRequest) (*emptypb.Empty, error) {
|
func (f *testIAMAdminServer) DeleteServiceAccountKey(ctx context.Context, req *adminpb.DeleteServiceAccountKeyRequest) (*emptypb.Empty, error) {
|
||||||
// Use queued delete results so tests can separately control deleting the old key and deleting the new key during rollback.
|
|
||||||
if len(f.testDeleteServiceAccountKeyErrors) > 0 {
|
|
||||||
err := f.testDeleteServiceAccountKeyErrors[0]
|
|
||||||
f.testDeleteServiceAccountKeyErrors = f.testDeleteServiceAccountKeyErrors[1:]
|
|
||||||
return &emptypb.Empty{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &emptypb.Empty{}, f.testDeleteServiceAccountKeyError
|
return &emptypb.Empty{}, f.testDeleteServiceAccountKeyError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package errors
|
package errors
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package errors
|
package errors
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package values
|
package values
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package values
|
package values
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/plugin/service/host"
|
"github.com/hashicorp/boundary-plugin-gcp/plugin/service/host"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure that HostPlugin implements the following services:
|
// Ensure that HostPlugin implements the following services:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
cred "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
cred "github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/errors"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/errors"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/values"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/values"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -108,7 +108,7 @@ func buildListInstancesRequest(attributes *SetAttributes, catalog *CatalogAttrib
|
|||||||
return nil, fmt.Errorf("error building filters: %w", err)
|
return nil, fmt.Errorf("error building filters: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(filters) > 0 {
|
if len(filters) > 1 {
|
||||||
filters := strings.Join(filters, " AND ")
|
filters := strings.Join(filters, " AND ")
|
||||||
request.Filter = &filters
|
request.Filter = &filters
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
cred "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
cred "github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -183,23 +183,6 @@ func TestBuildListInstancesRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedErrContains: "error building filters",
|
expectedErrContains: "error building filters",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "valid request with no filters",
|
|
||||||
attributes: &SetAttributes{
|
|
||||||
Filters: []string{},
|
|
||||||
},
|
|
||||||
catalog: &CatalogAttributes{
|
|
||||||
CredentialAttributes: &cred.CredentialAttributes{
|
|
||||||
ProjectId: "test-12345",
|
|
||||||
Zone: "us-central-1a",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: &computepb.ListInstancesRequest{
|
|
||||||
Project: "test-12345",
|
|
||||||
Zone: "us-central-1a",
|
|
||||||
Filter: stringPtr("status = running"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"google.golang.org/api/iterator"
|
"google.golang.org/api/iterator"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"cloud.google.com/go/compute/apiv1/computepb"
|
"cloud.google.com/go/compute/apiv1/computepb"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
errors "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/errors"
|
errors "github.com/hashicorp/boundary-plugin-gcp/internal/errors"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/hostsets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"google.golang.org/api/iterator"
|
"google.golang.org/api/iterator"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
@@ -87,22 +87,8 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
credential.IAMServiceAccountKeysDeletePermission,
|
credential.IAMServiceAccountKeysDeletePermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !credState.CredentialsConfig.IsRotatable() && !catalogAttributes.DisableCredentialRotation {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "cannot rotate credentials for non-rotatable credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
if credState.CredentialsConfig.IsRotatable() && !catalogAttributes.DisableCredentialRotation {
|
if credState.CredentialsConfig.IsRotatable() && !catalogAttributes.DisableCredentialRotation {
|
||||||
state, err := newGCPCatalogPersistedState(
|
if err := credState.CredentialsConfig.RotateServiceAccountKey(ctx, permissions, p.testGCPClientOpts...); err != nil {
|
||||||
append([]gcpCatalogPersistedStateOption{
|
|
||||||
withCredentials(credState),
|
|
||||||
}, p.testCatalogStateOpts...)...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "error setting up persisted state: %s", err)
|
|
||||||
}
|
|
||||||
validateCredsCallback := state.ValidateCredsCallbackFn(ctx, p.testGCPClientOpts...)
|
|
||||||
|
|
||||||
if err := credState.RotateCreds(ctx, permissions, validateCredsCallback, p.testGCPClientOpts...); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,15 +152,12 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "error getting persisted state from proto: %s", err)
|
return nil, status.Errorf(codes.InvalidArgument, "error getting persisted state from proto: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will be updating credentials when one of two changes occur:
|
|
||||||
// 1. The new catalog has a service account key, in which case we need to delete the old service account key credentials
|
|
||||||
// 2. The target service account ID has changed. This indicates a change of credential type to use service account impersonation
|
|
||||||
if newCatalog.GetSecrets() != nil || newCatalogAttributes.TargetServiceAccountId != oldCatalogAttributes.TargetServiceAccountId {
|
|
||||||
updatedCredentials, err := credential.GetCredentialsConfig(newCatalog.GetSecrets(), newCatalogAttributes.CredentialAttributes)
|
updatedCredentials, err := credential.GetCredentialsConfig(newCatalog.GetSecrets(), newCatalogAttributes.CredentialAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newCatalog.GetSecrets() != nil {
|
||||||
newCredState, err := credential.NewPersistedState([]credential.Option{
|
newCredState, err := credential.NewPersistedState([]credential.Option{
|
||||||
credential.WithCredentialsConfig(updatedCredentials),
|
credential.WithCredentialsConfig(updatedCredentials),
|
||||||
}...,
|
}...,
|
||||||
@@ -203,14 +186,11 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a validate check to make sure that we aren't trying to
|
if credState.CredentialsConfig.IsRotatable() {
|
||||||
// rotate credentials that are not rotatable. For example,
|
if !updatedCredentials.IsRotatable() && !newCatalogAttributes.DisableCredentialRotation {
|
||||||
// Application Default Credentials (ADC) are not rotatable.
|
|
||||||
if !credState.CredentialsConfig.IsRotatable() && !newCatalogAttributes.DisableCredentialRotation {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "cannot rotate credentials for non-rotatable credentials")
|
return nil, status.Error(codes.InvalidArgument, "cannot rotate credentials for non-rotatable credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
if credState.CredentialsConfig.IsRotatable() {
|
|
||||||
// This is a validate check to make sure that we aren't disabling
|
// This is a validate check to make sure that we aren't disabling
|
||||||
// rotation for credentials currently being managed by rotation.
|
// rotation for credentials currently being managed by rotation.
|
||||||
// This is not allowed.
|
// This is not allowed.
|
||||||
@@ -229,17 +209,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
// If we're enabling rotation now but didn't before, or have
|
// If we're enabling rotation now but didn't before, or have
|
||||||
// freshly replaced credentials, we can rotate here.
|
// freshly replaced credentials, we can rotate here.
|
||||||
if !newCatalogAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
|
if !newCatalogAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
|
||||||
state, err := newGCPCatalogPersistedState(
|
if err := credState.CredentialsConfig.RotateServiceAccountKey(ctx, permissions, p.testGCPClientOpts...); err != nil {
|
||||||
append([]gcpCatalogPersistedStateOption{
|
|
||||||
withCredentials(credState),
|
|
||||||
}, p.testCatalogStateOpts...)...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "error setting up persisted state: %s", err)
|
|
||||||
}
|
|
||||||
validateCredsCallback := state.ValidateCredsCallbackFn(ctx, p.testGCPClientOpts...)
|
|
||||||
|
|
||||||
if err := credState.RotateCreds(ctx, permissions, validateCredsCallback, p.testGCPClientOpts...); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,6 +587,11 @@ func validateSet(s *hostsets.HostSet) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
badFields := make(map[string]string)
|
badFields := make(map[string]string)
|
||||||
|
_, filterSet := attrMap[ConstListInstancesFilter]
|
||||||
|
|
||||||
|
if filterSet && len(attrs.Filters) == 0 {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstListInstancesFilter)] = "must not be empty"
|
||||||
|
}
|
||||||
|
|
||||||
for f := range attrMap {
|
for f := range attrMap {
|
||||||
if _, ok := allowedSetFields[f]; !ok {
|
if _, ok := allowedSetFields[f]; !ok {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -16,10 +16,10 @@ import (
|
|||||||
"cloud.google.com/go/iam/apiv1/iampb"
|
"cloud.google.com/go/iam/apiv1/iampb"
|
||||||
"cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"
|
"cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
cred "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
cred "github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/hostcatalogs"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/hostsets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -878,53 +878,6 @@ func TestCreateCatalog(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "create with application default credentials and credential rotation disabled",
|
|
||||||
req: &pb.OnCreateCatalogRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
||||||
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
||||||
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
||||||
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
||||||
nil,
|
|
||||||
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
||||||
testMockInstancesWithListInstancesError(nil),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
expectedRsp: &pb.OnCreateCatalogResponse{
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create with application default credentials and credential rotation enabled",
|
|
||||||
req: &pb.OnCreateCatalogRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
||||||
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
||||||
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(false),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErr: "cannot rotate credentials for non-rotatable credentials",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@@ -1219,12 +1172,6 @@ func TestUpdateCatalog(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Secrets: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
cred.ConstPrivateKeyId: structpb.NewStringValue(""),
|
|
||||||
cred.ConstPrivateKey: structpb.NewStringValue(""),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
Secrets: &structpb.Struct{
|
Secrets: &structpb.Struct{
|
||||||
@@ -1236,55 +1183,8 @@ func TestUpdateCatalog(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
||||||
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
||||||
nil,
|
|
||||||
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
||||||
testMockInstancesWithListInstancesError(nil),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
expectedErr: "cannot rotate credentials for non-rotatable credentials",
|
expectedErr: "cannot rotate credentials for non-rotatable credentials",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "update application default credentials",
|
|
||||||
req: &pb.OnUpdateCatalogRequest{
|
|
||||||
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
||||||
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
||||||
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
||||||
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NewCatalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: &structpb.Struct{
|
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
cred.ConstProjectId: structpb.NewStringValue("test-project-1"),
|
|
||||||
cred.ConstZone: structpb.NewStringValue("us-central1"),
|
|
||||||
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
||||||
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
||||||
nil,
|
|
||||||
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
||||||
testMockInstancesWithListInstancesError(nil),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
expectedRsp: &pb.OnUpdateCatalogResponse{
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: &structpb.Struct{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -11,9 +11,8 @@ import (
|
|||||||
compute "cloud.google.com/go/compute/apiv1"
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
"github.com/googleapis/gax-go/v2"
|
"github.com/googleapis/gax-go/v2"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"google.golang.org/api/iterator"
|
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
@@ -100,30 +99,3 @@ func (s *gcpCatalogPersistedState) InstancesClient(
|
|||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCredsCallbackFn returns a ValidateCredsCallback function that
|
|
||||||
// validates the credentials by listing instances in the project and zone.
|
|
||||||
func (s *gcpCatalogPersistedState) ValidateCredsCallbackFn(
|
|
||||||
ctx context.Context,
|
|
||||||
opts ...option.ClientOption,
|
|
||||||
) credential.ValidateCredsCallback {
|
|
||||||
return func(config *credential.Config, opts ...option.ClientOption) error {
|
|
||||||
s.CredentialsConfig = config
|
|
||||||
instancesClient, err := s.InstancesClient(ctx, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error creating instances client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
it := instancesClient.List(ctx, &computepb.ListInstancesRequest{
|
|
||||||
Project: s.CredentialsConfig.ProjectId,
|
|
||||||
Zone: s.CredentialsConfig.Zone,
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = it.Next()
|
|
||||||
if err != nil && err != iterator.Done {
|
|
||||||
return fmt.Errorf("error listing instances: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
@@ -89,44 +87,3 @@ func TestInstancesClient(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "error creating instances client")
|
assert.Contains(t, err.Error(), "error creating instances client")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func TestValidateCredsCallbackFn(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
cred := &credential.PersistedState{
|
|
||||||
CredentialsConfig: &credential.Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
Zone: "test-zone",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
state := &gcpCatalogPersistedState{PersistedState: cred}
|
|
||||||
|
|
||||||
t.Run("List instances error", func(t *testing.T) {
|
|
||||||
state.testInstancesAPIFunc = newTestMockInstances(ctx,
|
|
||||||
nil,
|
|
||||||
testMockInstancesWithListInstancesOutput(nil),
|
|
||||||
testMockInstancesWithListInstancesError(errors.New("error listing instances")),
|
|
||||||
)
|
|
||||||
|
|
||||||
callback := state.ValidateCredsCallbackFn(ctx)
|
|
||||||
err := callback(&credential.Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
Zone: "test-zone",
|
|
||||||
})
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "error listing instances")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Success", func(t *testing.T) {
|
|
||||||
state.testInstancesAPIFunc = newTestMockInstances(ctx,
|
|
||||||
nil,
|
|
||||||
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
||||||
testMockInstancesWithListInstancesError(nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
callback := state.ValidateCredsCallbackFn(ctx)
|
|
||||||
err := callback(&credential.Config{
|
|
||||||
ProjectId: "test-project-id",
|
|
||||||
Zone: "test-zone",
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package host
|
package host
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
compute "cloud.google.com/go/compute/apiv1"
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
"cloud.google.com/go/compute/apiv1/computepb"
|
"cloud.google.com/go/compute/apiv1/computepb"
|
||||||
"github.com/googleapis/gax-go/v2"
|
"github.com/googleapis/gax-go/v2"
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,589 +0,0 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/plugin/service/host"
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/hostcatalogs"
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/hostsets"
|
|
||||||
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
expectedInstanceCount = 3
|
|
||||||
expectedTagsCount = 3
|
|
||||||
expectedLabelsCount = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// All the tests in this test depend on each other
|
|
||||||
// to run in order. This is because the tests are
|
|
||||||
// testing the same plugin instance and the same
|
|
||||||
// persisted state. The tests are designed to run
|
|
||||||
// in order to test the plugin's ability to rotate
|
|
||||||
// credentials and manage the state of the plugin
|
|
||||||
// instance.
|
|
||||||
// To run the tests you need the following
|
|
||||||
// environment variables set:
|
|
||||||
// GOOGLE_APPLICATION_CREDENTIALS: This should be
|
|
||||||
// the path to the service account key. This can also
|
|
||||||
// be automatically set after authenticating with gcloud
|
|
||||||
// using `gcloud auth application-default login`.
|
|
||||||
//
|
|
||||||
// TF_VAR_project_id: The project_id of the project.
|
|
||||||
//
|
|
||||||
// TF_VAR_region: The region the resources should be
|
|
||||||
// deployed in.
|
|
||||||
//
|
|
||||||
// TF_VAR_zone: The zone the resources should be
|
|
||||||
// deployed in.
|
|
||||||
func TestHostPlugin(t *testing.T) {
|
|
||||||
creds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
||||||
if creds == "" {
|
|
||||||
t.Skip("set GOOGLE_APPLICATION_CREDENTIALS to use this test")
|
|
||||||
}
|
|
||||||
projectId := os.Getenv("TF_VAR_project_id")
|
|
||||||
if projectId == "" {
|
|
||||||
t.Skip("set TF_VAR_project_id to use this test")
|
|
||||||
}
|
|
||||||
region := os.Getenv("TF_VAR_region")
|
|
||||||
if region == "" {
|
|
||||||
t.Skip("set TF_VAR_region to use this test")
|
|
||||||
}
|
|
||||||
zone := os.Getenv("TF_VAR_zone")
|
|
||||||
if zone == "" {
|
|
||||||
t.Skip("set TF_VAR_zone to use this test")
|
|
||||||
}
|
|
||||||
|
|
||||||
require := require.New(t)
|
|
||||||
tf, err := NewTestTerraformer("testdata/host")
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(tf)
|
|
||||||
|
|
||||||
t.Log("===== deploying test Terraform workspace =====")
|
|
||||||
err = tf.Deploy()
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
t.Log("===== destroying test Terraform workspace =====")
|
|
||||||
if err := tf.Destroy(); err != nil {
|
|
||||||
t.Logf("WARNING: could not run Terraform destroy: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
clientEmail, err := tf.GetOutput("client_email")
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(clientEmail)
|
|
||||||
|
|
||||||
instanceIds, err := tf.GetOutputSlice("instance_ids")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(instanceIds, expectedInstanceCount)
|
|
||||||
|
|
||||||
instanceTags, err := tf.GetOutputMap("instance_tags")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(instanceTags, expectedInstanceCount)
|
|
||||||
|
|
||||||
instanceLabels, err := tf.GetOutputMap("instance_labels")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(instanceLabels, expectedLabelsCount)
|
|
||||||
|
|
||||||
privateKeys, err := tf.GetOutputSlice("private_keys")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(privateKeys, expectedServiceAccountKeys)
|
|
||||||
|
|
||||||
privateKeyIds, err := tf.GetOutputSlice("private_key_ids")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(privateKeyIds, expectedServiceAccountKeys)
|
|
||||||
|
|
||||||
p := new(host.HostPlugin)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var keyId, secret string
|
|
||||||
|
|
||||||
t.Run("OnCreateCatalog", func(t *testing.T) {
|
|
||||||
t.Run("Test non-rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnCreateCatalog(
|
|
||||||
ctx,
|
|
||||||
t,
|
|
||||||
p,
|
|
||||||
zone,
|
|
||||||
projectId,
|
|
||||||
clientEmail.(string),
|
|
||||||
privateKeyIds[0].(string),
|
|
||||||
privateKeys[0].(string),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Test rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnCreateCatalog(
|
|
||||||
ctx,
|
|
||||||
t,
|
|
||||||
p,
|
|
||||||
zone,
|
|
||||||
projectId,
|
|
||||||
clientEmail.(string),
|
|
||||||
keyId,
|
|
||||||
secret,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OnUpdateCatalog", func(t *testing.T) {
|
|
||||||
t.Run("Test no-op non-rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", false, false)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Switch to rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", false, true)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Test no-op with rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", true, true)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Switch credentials to next service account key. Don't rotate", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[1].(string), privateKeys[1].(string), true, false)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Switch credentials to next service account key. Add rotation", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[2].(string), privateKeys[2].(string), false, true)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Switch to next service account key, with rotation disabled", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[3].(string), privateKeys[3].(string), true, false)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Switch to another service account key and keep rotation off", func(t *testing.T) {
|
|
||||||
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[4].(string), privateKeys[4].(string), false, false)
|
|
||||||
require.NotNil(keyId)
|
|
||||||
require.NotNil(secret)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OnDeleteCatalog", func(t *testing.T) {
|
|
||||||
t.Run("Test non-rotated", func(t *testing.T) {
|
|
||||||
testPluginOnDeleteCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Test as if we had rotated these credentials.", func(t *testing.T) {
|
|
||||||
testPluginOnDeleteCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("HostSet", func(t *testing.T) {
|
|
||||||
keyId, secret := privateKeyIds[5].(string), privateKeys[5].(string)
|
|
||||||
expectedLabelInstancesMap := make(map[string][]string)
|
|
||||||
for instanceId, instanceLabel := range instanceLabels {
|
|
||||||
for tagKey := range instanceLabel.(map[string]any) {
|
|
||||||
for _, expectedTag := range instanceLabels {
|
|
||||||
if tagKey == expectedTag {
|
|
||||||
expectedLabelInstancesMap[tagKey] = append(expectedLabelInstancesMap[tagKey], instanceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := map[string][]string{
|
|
||||||
"label-1": {"foo"},
|
|
||||||
"label-2": {"bar"},
|
|
||||||
"label-3": {"baz"},
|
|
||||||
"label-4": {"foo", "bar"},
|
|
||||||
"label-5": {"foo", "baz"},
|
|
||||||
"label-6": {"bar", "baz"},
|
|
||||||
"label-7": {"foo", "bar", "baz"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, tc := range cases {
|
|
||||||
t.Run(fmt.Sprintf("Label=%s", key), func(t *testing.T) {
|
|
||||||
labels := make([]string, 0, len(tc))
|
|
||||||
for _, label := range tc {
|
|
||||||
labels = append(labels, fmt.Sprintf("labels.%s=%s", key, label))
|
|
||||||
}
|
|
||||||
testPluginOnCreateUpdateSet(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, labels)
|
|
||||||
testPluginListHosts(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, labels, expectedLabelInstancesMap)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPluginOnCreateCatalog(
|
|
||||||
ctx context.Context,
|
|
||||||
t *testing.T,
|
|
||||||
p *host.HostPlugin,
|
|
||||||
zone string,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string,
|
|
||||||
rotate bool) (string, string) {
|
|
||||||
t.Helper()
|
|
||||||
t.Logf("testing OnCreateCatalog (zone=%s, rotate=%t)", zone, rotate)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
reqAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: !rotate,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
reqSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: privateKeyId,
|
|
||||||
credential.ConstPrivateKey: privateKey,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
request := &pb.OnCreateCatalogRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: reqAttrs,
|
|
||||||
},
|
|
||||||
Secrets: reqSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response, err := p.OnCreateCatalog(ctx, request)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(response)
|
|
||||||
persisted := response.GetPersisted()
|
|
||||||
require.NotNil(persisted)
|
|
||||||
return validatePersistedSecrets(t, persisted.GetSecrets(), projectId, clientEmail, privateKeyId, privateKey, rotate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPluginOnUpdateCatalog(
|
|
||||||
ctx context.Context,
|
|
||||||
t *testing.T,
|
|
||||||
p *host.HostPlugin,
|
|
||||||
zone string,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
currentPrivateKeyId string,
|
|
||||||
currentPrivateKey string,
|
|
||||||
newPrivateKeyId string,
|
|
||||||
newPrivateKey string,
|
|
||||||
rotated bool,
|
|
||||||
rotate bool,
|
|
||||||
) (string, string) {
|
|
||||||
t.Helper()
|
|
||||||
t.Logf("testing OnUpdateCatalog (zone=%s, newcreds=%t, rotated=%t, rotate=%t)", zone, newPrivateKeyId != "" && newPrivateKey != "", rotated, rotate)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
// Take a timestamp of the current time to get a point in time to
|
|
||||||
// reference, ensuring that we are updating credential rotation
|
|
||||||
// timestamps.
|
|
||||||
currentCredsLastRotatedTime := time.Now()
|
|
||||||
|
|
||||||
reqCurrentAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: !rotated,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
reqNewAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: !rotate,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
var reqSecrets *structpb.Struct
|
|
||||||
if newPrivateKeyId != "" && newPrivateKey != "" {
|
|
||||||
reqSecrets, err = structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: newPrivateKeyId,
|
|
||||||
credential.ConstPrivateKey: newPrivateKey,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
}
|
|
||||||
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: currentPrivateKeyId,
|
|
||||||
credential.ConstPrivateKey: currentPrivateKey,
|
|
||||||
credential.ConstCredsLastRotatedTime: func() string {
|
|
||||||
if rotated {
|
|
||||||
return currentCredsLastRotatedTime.Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (time.Time{}).Format(time.RFC3339Nano)
|
|
||||||
}(),
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(reqPersistedSecrets)
|
|
||||||
request := &pb.OnUpdateCatalogRequest{
|
|
||||||
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: reqCurrentAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NewCatalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: reqNewAttrs,
|
|
||||||
},
|
|
||||||
Secrets: reqSecrets,
|
|
||||||
},
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: reqPersistedSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response, err := p.OnUpdateCatalog(ctx, request)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(response)
|
|
||||||
persisted := response.GetPersisted()
|
|
||||||
require.NotNil(persisted)
|
|
||||||
return validateUpdateSecrets(
|
|
||||||
t,
|
|
||||||
projectId,
|
|
||||||
clientEmail,
|
|
||||||
persisted.GetSecrets(),
|
|
||||||
currentCredsLastRotatedTime,
|
|
||||||
currentPrivateKeyId,
|
|
||||||
currentPrivateKey,
|
|
||||||
newPrivateKeyId,
|
|
||||||
newPrivateKey,
|
|
||||||
rotated,
|
|
||||||
rotate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPluginOnDeleteCatalog(
|
|
||||||
ctx context.Context,
|
|
||||||
t *testing.T,
|
|
||||||
p *host.HostPlugin,
|
|
||||||
zone string,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string,
|
|
||||||
rotated bool) {
|
|
||||||
t.Helper()
|
|
||||||
t.Logf("testing OnDeleteCatalog (zone=%s, rotated=%t)", zone, rotated)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
reqAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: !rotated,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
reqSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: privateKeyId,
|
|
||||||
credential.ConstPrivateKey: privateKey,
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: privateKeyId,
|
|
||||||
credential.ConstPrivateKey: privateKey,
|
|
||||||
credential.ConstCredsLastRotatedTime: func() string {
|
|
||||||
if rotated {
|
|
||||||
return time.Now().Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (time.Time{}).Format(time.RFC3339Nano)
|
|
||||||
}(),
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
request := &pb.OnDeleteCatalogRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: reqAttrs,
|
|
||||||
},
|
|
||||||
Secrets: reqSecrets,
|
|
||||||
},
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: reqPersistedSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response, err := p.OnDeleteCatalog(ctx, request)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(response)
|
|
||||||
|
|
||||||
// We want to test the validity of the credentials post-deletion.
|
|
||||||
if rotated {
|
|
||||||
// The credentials should no longer be valid.
|
|
||||||
requireCredentialsInvalid(t, projectId, clientEmail, privateKeyId, privateKey)
|
|
||||||
} else {
|
|
||||||
// The credentials should still be valid. Sleep 10s first just to
|
|
||||||
// be sure, since we're not rotating.
|
|
||||||
time.Sleep(time.Second * 10)
|
|
||||||
requireCredentialsValid(t, projectId, clientEmail, privateKeyId, privateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPluginOnCreateUpdateSet(
|
|
||||||
ctx context.Context,
|
|
||||||
t *testing.T,
|
|
||||||
p *host.HostPlugin,
|
|
||||||
zone string,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string,
|
|
||||||
labels []string) {
|
|
||||||
t.Helper()
|
|
||||||
t.Logf("testing OnCreateSet (zone=%s, labels=%v)", zone, labels)
|
|
||||||
require := require.New(t)
|
|
||||||
catalogAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: true, // Note that this does nothing in sets, but just noting for tests
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
setAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
host.ConstListInstancesFilter: []any{strings.Join(labels, " AND ")},
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: privateKeyId,
|
|
||||||
credential.ConstPrivateKey: privateKey,
|
|
||||||
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
createRequest := &pb.OnCreateSetRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: catalogAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Set: &hostsets.HostSet{
|
|
||||||
Attrs: &hostsets.HostSet_Attributes{
|
|
||||||
Attributes: setAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: reqPersistedSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
createResponse, err := p.OnCreateSet(ctx, createRequest)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(createResponse)
|
|
||||||
|
|
||||||
// Do an update test in the same function, as it's pretty much the
|
|
||||||
// same function right now.
|
|
||||||
t.Logf("testing OnUpdateSet (zone=%s, labels=%v)", zone, labels)
|
|
||||||
updateRequest := &pb.OnUpdateSetRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: catalogAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CurrentSet: &hostsets.HostSet{
|
|
||||||
Attrs: &hostsets.HostSet_Attributes{
|
|
||||||
Attributes: setAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NewSet: &hostsets.HostSet{
|
|
||||||
Attrs: &hostsets.HostSet_Attributes{
|
|
||||||
Attributes: setAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: reqPersistedSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
updateResponse, err := p.OnUpdateSet(ctx, updateRequest)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(updateResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPluginListHosts(
|
|
||||||
ctx context.Context,
|
|
||||||
t *testing.T,
|
|
||||||
p *host.HostPlugin,
|
|
||||||
zone string,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string,
|
|
||||||
labels []string,
|
|
||||||
expected map[string][]string) {
|
|
||||||
t.Helper()
|
|
||||||
t.Logf("testing ListHosts (zone=%s, labels=%v)", zone, labels)
|
|
||||||
require := require.New(t)
|
|
||||||
catalogAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstProjectId: projectId,
|
|
||||||
credential.ConstClientEmail: clientEmail,
|
|
||||||
credential.ConstZone: zone,
|
|
||||||
credential.ConstDisableCredentialRotation: true, // Note that this does nothing in sets, but just noting for tests
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
sets := make([]*hostsets.HostSet, len(labels))
|
|
||||||
for i, label := range labels {
|
|
||||||
setAttrs, err := structpb.NewStruct(map[string]any{
|
|
||||||
host.ConstListInstancesFilter: []any{label},
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
sets[i] = &hostsets.HostSet{
|
|
||||||
Id: fmt.Sprintf("hostset-%d", i),
|
|
||||||
Attrs: &hostsets.HostSet_Attributes{
|
|
||||||
Attributes: setAttrs,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
||||||
credential.ConstPrivateKeyId: privateKeyId,
|
|
||||||
credential.ConstPrivateKey: privateKey,
|
|
||||||
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
request := &pb.ListHostsRequest{
|
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
||||||
Attributes: catalogAttrs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Sets: sets,
|
|
||||||
Persisted: &pb.HostCatalogPersisted{
|
|
||||||
Secrets: reqPersistedSecrets,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response, err := p.ListHosts(ctx, request)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(response)
|
|
||||||
|
|
||||||
// Validate the returned instances by ID. Assemble the instance
|
|
||||||
// details from the expected set.
|
|
||||||
expectedInstances := make(map[string][]string)
|
|
||||||
for i, label := range labels {
|
|
||||||
for _, instanceId := range expected[label] {
|
|
||||||
expectedInstances[instanceId] = append(expectedInstances[instanceId], fmt.Sprintf("hostset-%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the returned hosts by ID and create the same kind of map.
|
|
||||||
actualInstances := make(map[string][]string)
|
|
||||||
for _, host := range response.GetHosts() {
|
|
||||||
actualInstances[host.ExternalId] = host.SetIds
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare
|
|
||||||
require.Equal(expectedInstances, actualInstances)
|
|
||||||
// Success
|
|
||||||
t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", zone, labels, len(actualInstances), actualInstances)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
data "google_compute_zones" "available" {}
|
||||||
|
|
||||||
|
resource "google_compute_instance" "default" {
|
||||||
|
count = var.num_instances
|
||||||
|
name = "boundary-${count.index}"
|
||||||
|
machine_type = "n2-standard-2"
|
||||||
|
zone = data.google_compute_zones.available.names[0]
|
||||||
|
|
||||||
|
labels = var.labels
|
||||||
|
|
||||||
|
boot_disk {
|
||||||
|
initialize_params {
|
||||||
|
image = "ubuntu-os-cloud/ubuntu-2204-lts"
|
||||||
|
labels = var.labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "default"
|
||||||
|
|
||||||
|
access_config {
|
||||||
|
// Ephemeral public IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
ssh-keys = "ubuntu:${tls_private_key.ssh.public_key_openssh}"
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "service_account" {
|
||||||
|
for_each = var.service_account_email != null ? [var.service_account_email] : []
|
||||||
|
content {
|
||||||
|
email = service_account.value
|
||||||
|
scopes = ["cloud-platform"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance_group" "webservers" {
|
||||||
|
name = "boundary-servers"
|
||||||
|
description = "Boundary server group"
|
||||||
|
|
||||||
|
instances = google_compute_instance.default.*.id
|
||||||
|
|
||||||
|
zone = data.google_compute_zones.available.names[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RSA key of size 4096 bits
|
||||||
|
resource "tls_private_key" "ssh" {
|
||||||
|
algorithm = "RSA"
|
||||||
|
rsa_bits = 4096
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_firewall" "rules" {
|
||||||
|
name = "allow-ssh"
|
||||||
|
network = "default"
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "tcp"
|
||||||
|
ports = ["22"]
|
||||||
|
}
|
||||||
|
source_ranges = [var.client_cidr_block]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
resource "local_file" "ssh_key" {
|
||||||
|
content = tls_private_key.ssh.private_key_openssh
|
||||||
|
filename = "${path.module}/secrets/id_rsa.pem"
|
||||||
|
file_permission = "0400"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright IBM Corp. 2024, 2026
|
# Copyright (c) HashiCorp, Inc.
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
@@ -12,5 +12,4 @@ terraform {
|
|||||||
|
|
||||||
provider "google" {
|
provider "google" {
|
||||||
region = var.region
|
region = var.region
|
||||||
project = var.project_id
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
variable "labels" {
|
||||||
|
type = map(string)
|
||||||
|
default = {
|
||||||
|
repository = "boundary-plugin-gcp"
|
||||||
|
purpose = "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "region" {
|
||||||
|
type = string
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "num_instances" {
|
||||||
|
type = number
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_email" {
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "client_cidr_block" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
Vendored
-77
@@ -1,77 +0,0 @@
|
|||||||
# Copyright IBM Corp. 2024, 2026
|
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
resource "random_id" "service_account_client_email" {
|
|
||||||
prefix = "test-${var.user_prefix}"
|
|
||||||
byte_length = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_service_account" "test_service_account" {
|
|
||||||
project = var.project_id
|
|
||||||
account_id = random_id.service_account_client_email.dec
|
|
||||||
display_name = "test-${var.user_prefix}"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_project_iam_binding" "test_service_account_user" {
|
|
||||||
project = var.project_id
|
|
||||||
count = length(var.rolesList)
|
|
||||||
role = var.rolesList[count.index]
|
|
||||||
members = [
|
|
||||||
"serviceAccount:${google_service_account.test_service_account.email}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_service_account_key" "test_service_account_key" {
|
|
||||||
service_account_id = google_service_account.test_service_account.name
|
|
||||||
public_key_type = "TYPE_X509_PEM_FILE"
|
|
||||||
private_key_type = "TYPE_GOOGLE_CREDENTIALS_FILE"
|
|
||||||
key_algorithm = "KEY_ALG_RSA_2048"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_service_account_key" "test_service_account_keys" {
|
|
||||||
count = var.num_service_account_keys
|
|
||||||
service_account_id = google_service_account.test_service_account.name
|
|
||||||
public_key_type = "TYPE_X509_PEM_FILE"
|
|
||||||
private_key_type = "TYPE_GOOGLE_CREDENTIALS_FILE"
|
|
||||||
key_algorithm = "KEY_ALG_RSA_2048"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_compute_instance" "instances" {
|
|
||||||
project = var.project_id
|
|
||||||
count = var.num_instances
|
|
||||||
name = "boundary-${count.index}"
|
|
||||||
machine_type = "n2-standard-2"
|
|
||||||
zone = var.zone
|
|
||||||
labels = var.labels
|
|
||||||
tags = ["test-instance-${count.index}"]
|
|
||||||
|
|
||||||
boot_disk {
|
|
||||||
initialize_params {
|
|
||||||
image = "ubuntu-os-cloud/ubuntu-2204-lts"
|
|
||||||
labels = var.labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
network_interface {
|
|
||||||
network = "default"
|
|
||||||
|
|
||||||
access_config {
|
|
||||||
// Ephemeral public IP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata = {
|
|
||||||
ssh-keys = "ubuntu:${tls_private_key.ssh.public_key_openssh}"
|
|
||||||
}
|
|
||||||
|
|
||||||
service_account {
|
|
||||||
email = google_service_account.test_service_account.email
|
|
||||||
scopes = ["cloud-platform"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# RSA key of size 4096 bits
|
|
||||||
resource "tls_private_key" "ssh" {
|
|
||||||
algorithm = "RSA"
|
|
||||||
rsa_bits = 4096
|
|
||||||
}
|
|
||||||
Vendored
-32
@@ -1,32 +0,0 @@
|
|||||||
# Copyright IBM Corp. 2024, 2026
|
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
output "client_email" {
|
|
||||||
value = google_service_account.test_service_account.email
|
|
||||||
}
|
|
||||||
|
|
||||||
output "private_keys" {
|
|
||||||
value = [for key in google_service_account_key.test_service_account_keys : jsondecode(base64decode(key.private_key)).private_key]
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
output "private_key_ids" {
|
|
||||||
value = [for key in google_service_account_key.test_service_account_keys : chomp(reverse(split("/", key.id))[0])]
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
output "instance_ids" {
|
|
||||||
value = google_compute_instance.instances.*.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "instance_tags" {
|
|
||||||
value = {
|
|
||||||
for instance in google_compute_instance.instances : instance.id => instance.tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "instance_labels" {
|
|
||||||
value = {
|
|
||||||
for instance in google_compute_instance.instances : instance.id => instance.labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-50
@@ -1,50 +0,0 @@
|
|||||||
# Copyright IBM Corp. 2024, 2026
|
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
data "google_client_openid_userinfo" "current" {}
|
|
||||||
|
|
||||||
variable "labels" {
|
|
||||||
type = map(string)
|
|
||||||
default = {
|
|
||||||
repository = "boundary-plugin-gcp"
|
|
||||||
purpose = "testing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "project_id" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "region" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "zone" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "num_instances" {
|
|
||||||
type = number
|
|
||||||
default = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "service_account_email" {
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "rolesList" {
|
|
||||||
type = list(string)
|
|
||||||
description = "List of roles to assign to the service account"
|
|
||||||
default = ["roles/compute.viewer","roles/iam.serviceAccountKeyAdmin"]
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "user_prefix" {
|
|
||||||
type = string
|
|
||||||
default = "boundary-gcp-plugin"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "num_service_account_keys" {
|
|
||||||
type = number
|
|
||||||
default = 6
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
// Copyright IBM Corp. 2024, 2026
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
|
||||||
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/values"
|
|
||||||
tfjson "github.com/hashicorp/terraform-json"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/api/option"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// expectedServiceAccountKeys is the number of service account keys
|
|
||||||
// expected to be created in the Terraform project.
|
|
||||||
expectedServiceAccountKeys = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestTerraformer is an object representing the lifecycle of a
|
|
||||||
// particular Terraform project.
|
|
||||||
//
|
|
||||||
// Note that Terraform must be installed in the system PATH (not to
|
|
||||||
// be confused with the working directory below). This object does
|
|
||||||
// not manage anything with regards to installation or version
|
|
||||||
// management.
|
|
||||||
type TestTerraformer struct {
|
|
||||||
// The full path to the Terraform binary, set during initialization
|
|
||||||
// to the binary found in the system path.
|
|
||||||
tfPath string
|
|
||||||
|
|
||||||
// The working directory for Terraform commands.
|
|
||||||
workDir string
|
|
||||||
|
|
||||||
// The Terraform state. Use the Output method to look up particular
|
|
||||||
// outputs from the Terraform state.
|
|
||||||
state *tfjson.State
|
|
||||||
|
|
||||||
// Keeps track of whether or not we have run the init command
|
|
||||||
// during this run of Terraform. This only needs to be done once.
|
|
||||||
// Do not use initErr directly; it stores the init error if one was
|
|
||||||
// encountered on init.
|
|
||||||
initOnce sync.Once
|
|
||||||
initErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestTerraformer initializes a Terraform directory. It just
|
|
||||||
// returns an initialized project, it does not perform any actions on
|
|
||||||
// it.
|
|
||||||
func NewTestTerraformer(workDir string) (*TestTerraformer, error) {
|
|
||||||
tfPath, err := exec.LookPath("terraform")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("LookPath error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if workDir == "" {
|
|
||||||
return nil, errors.New("workDir is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TestTerraformer{
|
|
||||||
tfPath: tfPath,
|
|
||||||
workDir: workDir,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deploy runs the following commands, in order: init, apply, plan,
|
|
||||||
// json. The output of the JSON command is then imported to the
|
|
||||||
// internal state.
|
|
||||||
func (t *TestTerraformer) Deploy() error {
|
|
||||||
if err := t.init(); err != nil {
|
|
||||||
return fmt.Errorf("error running Terraform init: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.apply(); err != nil {
|
|
||||||
return fmt.Errorf("error running Terraform apply: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.plan(); err != nil {
|
|
||||||
return fmt.Errorf("error running Terraform plan: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := t.showJson()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error getting Terraform plan JSON data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := new(tfjson.Plan)
|
|
||||||
if err := json.Unmarshal(output, newState); err != nil {
|
|
||||||
return fmt.Errorf("error reading new Terraform plan JSON data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.state = newState.PriorState
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy runs the following commands, in order: init, destroy. The
|
|
||||||
// state is also cleared.
|
|
||||||
func (t *TestTerraformer) Destroy() error {
|
|
||||||
if err := t.init(); err != nil {
|
|
||||||
return fmt.Errorf("error running Terraform init: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.destroy(); err != nil {
|
|
||||||
return fmt.Errorf("error running Terraform destroy: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.state = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutput returns the value of the output located at key.
|
|
||||||
//
|
|
||||||
// Outputs for the root module are supported only. It's an error to
|
|
||||||
// run this when no state exists.
|
|
||||||
func (t *TestTerraformer) GetOutput(key string) (any, error) {
|
|
||||||
if key == "" {
|
|
||||||
return nil, errors.New("key must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.state == nil {
|
|
||||||
return nil, errors.New("state is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.state.Values == nil {
|
|
||||||
return nil, errors.New("state integrity error: no state values")
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.state.Values.Outputs == nil {
|
|
||||||
return nil, errors.New("state integrity error: outputs is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
output, ok := t.state.Values.Outputs[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("output with key %q not found", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if output == nil {
|
|
||||||
return nil, fmt.Errorf("state integrity error: output at key %q is nil", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutputString wraps GetOutput and returns the value as a string. It's an
|
|
||||||
// error if the return value is actually not a string.
|
|
||||||
func (t *TestTerraformer) GetOutputString(key string) (string, error) {
|
|
||||||
value, err := t.GetOutput(key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
str, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("expected output value at key %q to be string, got %T", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutputMap wraps GetOutput and returns the value as a map. It's an
|
|
||||||
// error if the return value is actually not a map.
|
|
||||||
func (t *TestTerraformer) GetOutputMap(key string) (map[string]any, error) {
|
|
||||||
value, err := t.GetOutput(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := value.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("expected output value at key %q to be map, got %T", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutputSlice wraps GetOutput and returns the value as a slice. It's an
|
|
||||||
// error if the return value is actually not a slice.
|
|
||||||
func (t *TestTerraformer) GetOutputSlice(key string) ([]any, error) {
|
|
||||||
value, err := t.GetOutput(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := value.([]any)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("expected output value at key %q to be slice, got %T", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// init runs "terraform init" in the project's working directory.
|
|
||||||
//
|
|
||||||
// This is only ever done once.
|
|
||||||
func (t *TestTerraformer) init() error {
|
|
||||||
t.initOnce.Do(func() {
|
|
||||||
cmd := exec.Command(t.tfPath, "init")
|
|
||||||
cmd.Dir = t.workDir
|
|
||||||
stdoutStderr, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.initErr = fmt.Errorf("%s\n%s", stdoutStderr, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return t.initErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply runs "terraform apply" in the project's working directory.
|
|
||||||
func (t *TestTerraformer) apply() error {
|
|
||||||
cmd := exec.Command(t.tfPath, "apply", "-input=false", "-auto-approve")
|
|
||||||
cmd.Dir = t.workDir
|
|
||||||
stdoutStderr, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s\n%s", stdoutStderr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// destroy runs "terraform destroy" in the project's working directory.
|
|
||||||
func (t *TestTerraformer) destroy() error {
|
|
||||||
cmd := exec.Command(t.tfPath, "destroy", "-input=false", "-auto-approve")
|
|
||||||
cmd.Dir = t.workDir
|
|
||||||
stdoutStderr, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s\n%s", stdoutStderr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// plan runs "terraform plan" in the project's working directory.
|
|
||||||
//
|
|
||||||
// Note that plan is not run as part of the apply workflow; rather,
|
|
||||||
// it's run to get the data for for importing the state via
|
|
||||||
// terraform-json. The apply method ignores the plan generated here.
|
|
||||||
func (t *TestTerraformer) plan() error {
|
|
||||||
cmd := exec.Command(t.tfPath, "plan", "-input=false", "-out=plan.tfplan")
|
|
||||||
cmd.Dir = t.workDir
|
|
||||||
stdoutStderr, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s\n%s", stdoutStderr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// showJson runs "terraform show -json". The output is returned along
|
|
||||||
// with any error.
|
|
||||||
//
|
|
||||||
// Output is only the results of stdout and should be suitable for
|
|
||||||
// parsing.
|
|
||||||
//
|
|
||||||
// This method expects the plan.tfplan file to be generated in the
|
|
||||||
// working directory already. This can be done with the plan method.
|
|
||||||
func (t *TestTerraformer) showJson() ([]byte, error) {
|
|
||||||
cmd := exec.Command(t.tfPath, "show", "-json", "plan.tfplan")
|
|
||||||
cmd.Dir = t.workDir
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
exitErr, ok := err.(*exec.ExitError)
|
|
||||||
if !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("%s\n%s", exitErr.Stderr, exitErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireCredentialsInvalid(
|
|
||||||
t *testing.T,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string) {
|
|
||||||
t.Helper()
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
c, err := credential.NewConfig(
|
|
||||||
credential.WithProjectId(projectId),
|
|
||||||
credential.WithClientEmail(clientEmail),
|
|
||||||
credential.WithPrivateKeyId(privateKeyId),
|
|
||||||
credential.WithPrivateKey(privateKey))
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
creds, err := c.GenerateCredentials(context.Background())
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
// We need to wait for invalidation as while gcp waits for
|
|
||||||
// credential creation, deletion of the old credentials returns
|
|
||||||
// immediately.
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
|
||||||
defer cancel()
|
|
||||||
waitErr:
|
|
||||||
for {
|
|
||||||
_, err = c.ValidateIamPermissions(timeoutCtx, []string{credential.ComputeInstancesListPermission}, option.WithTokenSource(creds.TokenSource))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
// pass
|
|
||||||
|
|
||||||
case <-timeoutCtx.Done():
|
|
||||||
break waitErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Error(err)
|
|
||||||
require.Contains(err.Error(), "failed to validate IAM permissions")
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireCredentialsValid(
|
|
||||||
t *testing.T,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string) {
|
|
||||||
t.Helper()
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
c, err := credential.NewConfig(
|
|
||||||
credential.WithProjectId(projectId),
|
|
||||||
credential.WithClientEmail(clientEmail),
|
|
||||||
credential.WithPrivateKeyId(privateKeyId),
|
|
||||||
credential.WithPrivateKey(privateKey))
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
creds, err := c.GenerateCredentials(context.Background())
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
_, err = c.ValidateIamPermissions(context.Background(), []string{credential.ComputeInstancesListPermission}, option.WithTokenSource(creds.TokenSource))
|
|
||||||
require.NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validatePersistedSecrets(
|
|
||||||
t *testing.T,
|
|
||||||
persistedSecrets *structpb.Struct,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
privateKeyId string,
|
|
||||||
privateKey string,
|
|
||||||
rotate bool) (string, string) {
|
|
||||||
t.Helper()
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
require.NotNil(persistedSecrets)
|
|
||||||
persistedPrivateKeyId, err := values.GetStringValue(persistedSecrets, credential.ConstPrivateKeyId, true)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotZero(persistedPrivateKeyId)
|
|
||||||
if rotate {
|
|
||||||
require.NotEqual(privateKeyId, persistedPrivateKeyId)
|
|
||||||
} else {
|
|
||||||
require.Equal(privateKeyId, persistedPrivateKeyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
persistedPrivateKey, err := values.GetStringValue(persistedSecrets, credential.ConstPrivateKey, true)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotZero(persistedPrivateKey)
|
|
||||||
if rotate {
|
|
||||||
require.NotEqual(privateKey, persistedPrivateKey)
|
|
||||||
} else {
|
|
||||||
require.Equal(privateKey, persistedPrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
persistedCredsLastRotatedTime, err := values.GetTimeValue(persistedSecrets, credential.ConstCredsLastRotatedTime)
|
|
||||||
require.NoError(err)
|
|
||||||
if rotate {
|
|
||||||
require.NotZero(persistedCredsLastRotatedTime)
|
|
||||||
requireCredentialsInvalid(t, projectId, clientEmail, privateKeyId, privateKey)
|
|
||||||
} else {
|
|
||||||
require.Zero(persistedCredsLastRotatedTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return persistedPrivateKeyId, persistedPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateUpdateSecrets(
|
|
||||||
t *testing.T,
|
|
||||||
projectId string,
|
|
||||||
clientEmail string,
|
|
||||||
persistedSecrets *structpb.Struct,
|
|
||||||
currentCredsLastRotatedTime time.Time,
|
|
||||||
currentPrivateKeyId string,
|
|
||||||
currentPrivateKey string,
|
|
||||||
newPrivateKeyId string,
|
|
||||||
newPrivateKey string,
|
|
||||||
rotated bool,
|
|
||||||
rotate bool) (string, string) {
|
|
||||||
t.Helper()
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
// Complex checks based on the scenarios.
|
|
||||||
persistedPrivateKeyId, err := values.GetStringValue(persistedSecrets, credential.ConstPrivateKeyId, true)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotZero(persistedPrivateKeyId)
|
|
||||||
persistedPrivateKey, err := values.GetStringValue(persistedSecrets, credential.ConstPrivateKey, true)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotZero(persistedPrivateKey)
|
|
||||||
persistedCredsLastRotatedTime, err := values.GetTimeValue(persistedSecrets, credential.ConstCredsLastRotatedTime)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
// Our test scenarios are complex due the multi-dimensional nature
|
|
||||||
// of criteria, so we lay them out in a switch below.
|
|
||||||
switch {
|
|
||||||
case newPrivateKeyId != "" && rotated && rotate:
|
|
||||||
// The new private key ID was provided, we had previously rotated
|
|
||||||
// the credentials before, and the new credential set is to be
|
|
||||||
// rotated as well. In this case, the old credentials should have
|
|
||||||
// been deleted, and the new credentials should have been rotated,
|
|
||||||
// hence, should not match the new credentials initially
|
|
||||||
// provided. Rotation time should be non-zero and updated.
|
|
||||||
requireCredentialsInvalid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.NotEqual(persistedPrivateKeyId, newPrivateKeyId)
|
|
||||||
require.NotEqual(persistedPrivateKey, newPrivateKey)
|
|
||||||
require.NotZero(persistedCredsLastRotatedTime)
|
|
||||||
require.True(persistedCredsLastRotatedTime.After(currentCredsLastRotatedTime))
|
|
||||||
|
|
||||||
case newPrivateKeyId != "" && rotated && !rotate:
|
|
||||||
// The new private key ID was provided, we had previously rotated
|
|
||||||
// the credentials before, and the new credential is *not*
|
|
||||||
// rotated. In this case, the old credentials should have
|
|
||||||
// been deleted, But the new credentials should have not been
|
|
||||||
// rotated, and hence should be the same. Rotation time should be
|
|
||||||
// zero.
|
|
||||||
requireCredentialsInvalid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.Equal(persistedPrivateKeyId, newPrivateKeyId)
|
|
||||||
require.Equal(persistedPrivateKey, newPrivateKey)
|
|
||||||
require.Zero(persistedCredsLastRotatedTime)
|
|
||||||
|
|
||||||
case newPrivateKeyId != "" && !rotated && rotate:
|
|
||||||
// The new private key was provided, we *have not* previously
|
|
||||||
// rotated the credentials, and the new credential set is to be
|
|
||||||
// rotated. In this case, the old credentials should have been
|
|
||||||
// left alone, and the new credentials should have been rotated,
|
|
||||||
// hence, should not match the new credentials initially
|
|
||||||
// provided. Rotation time should be non-zero, but updated.
|
|
||||||
requireCredentialsValid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.NotEqual(persistedPrivateKeyId, newPrivateKeyId)
|
|
||||||
require.NotEqual(persistedPrivateKey, newPrivateKey)
|
|
||||||
require.NotZero(persistedCredsLastRotatedTime)
|
|
||||||
require.True(persistedCredsLastRotatedTime.After(currentCredsLastRotatedTime))
|
|
||||||
|
|
||||||
case newPrivateKeyId != "" && !rotated && !rotate:
|
|
||||||
// The new private key was provided, but we have not rotated
|
|
||||||
// the credentials previously and we still don't plan on rotating
|
|
||||||
// them. In this case, the old credentials should still be valid,
|
|
||||||
// and the persisted ones should match the new ones provided.
|
|
||||||
// Rotation time should be zero.
|
|
||||||
requireCredentialsValid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.Equal(persistedPrivateKeyId, newPrivateKeyId)
|
|
||||||
require.Equal(persistedPrivateKey, newPrivateKey)
|
|
||||||
require.Zero(persistedCredsLastRotatedTime)
|
|
||||||
|
|
||||||
case newPrivateKeyId == "" && rotated && rotate:
|
|
||||||
// No new credentials have been provided, but we have previously
|
|
||||||
// rotated and are still rotating credentials. This is a no-op.
|
|
||||||
// Existing credentials should still be valid and match the ones
|
|
||||||
// persisted to state. Rotation time should be identical since
|
|
||||||
// no new rotation occurred.
|
|
||||||
requireCredentialsValid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.Equal(persistedPrivateKeyId, currentPrivateKeyId)
|
|
||||||
require.Equal(persistedPrivateKey, currentPrivateKey)
|
|
||||||
require.NotZero(persistedCredsLastRotatedTime)
|
|
||||||
require.True(currentCredsLastRotatedTime.Equal(persistedCredsLastRotatedTime))
|
|
||||||
|
|
||||||
case newPrivateKeyId == "" && rotated && !rotate:
|
|
||||||
// No new credentials have been provided, and we have previously
|
|
||||||
// rotated the credentials. This is actually an error, but we
|
|
||||||
// don't test it here; it's covered in unit testing (see
|
|
||||||
// plugin_test.go).
|
|
||||||
require.FailNow("testing rotated-to-not-rotated scenario not implemented by this helper")
|
|
||||||
|
|
||||||
case newPrivateKeyId == "" && !rotated && rotate:
|
|
||||||
// No new credentials have been provided, and while we did not
|
|
||||||
// rotate before, we want to switch to rotation. In this case,
|
|
||||||
// the existing persisted credentials should have been rotated,
|
|
||||||
// with a new non-zero timestamp.
|
|
||||||
requireCredentialsInvalid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.NotEqual(persistedPrivateKeyId, currentPrivateKeyId)
|
|
||||||
require.NotEqual(persistedPrivateKey, currentPrivateKey)
|
|
||||||
require.NotZero(persistedCredsLastRotatedTime)
|
|
||||||
|
|
||||||
case newPrivateKeyId == "" && !rotated && !rotate:
|
|
||||||
// No new credentials have been provided and we have not, nor do
|
|
||||||
// not, plan on rotating the credentials. This is a no-op.
|
|
||||||
// Existing credentials should still be valid and match the ones
|
|
||||||
// persisted to state. Rotation time should remain at zero.
|
|
||||||
requireCredentialsValid(t, projectId, clientEmail, currentPrivateKeyId, currentPrivateKey)
|
|
||||||
require.Equal(persistedPrivateKeyId, currentPrivateKeyId)
|
|
||||||
require.Equal(persistedPrivateKey, currentPrivateKey)
|
|
||||||
require.Zero(persistedCredsLastRotatedTime)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Scenario was reached that was not covered by this function.
|
|
||||||
require.FailNow("unknown test scenario")
|
|
||||||
}
|
|
||||||
|
|
||||||
return persistedPrivateKeyId, persistedPrivateKey
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user