Skip to content

Commit ba62711

Browse files
committed
roachprod: refactor IAP authentication
Previously, the authentication mechanism for testeng Identity-Aware Proxy protected endpoints was relying on a shared ervice account key accessed via a GCS bucket. This was causing the need for secret rotation and was limiting auditability. This patch introduces a new mechanism based on short-lived OAuth tokens and service account impersonation through the default local credentials. The identity of the caller is determined with the following precedence: 1. `GOOGLE_EPHEMERAL_CREDENTIALS` environment variable 2. Application Default Credentials (ADC): a. `GOOGLE_APPLICATION_CREDENTIALS` environment variable b. Default service account (application_default_credentials.json) file c. App Engine standard environment d. GCE metadata server 3. `gcloud config config-helper` output The caller needs to have the `roles/iam.serviceAccountTokenCreator` role on the service account to be able to impersonate the service account and generate short lived OAuth AccessTokens. Both `promhelperclient` and `grafana annotations` switch to this new method, via the new `IAPTokenSourceIface` interface that handles the service account impersonation, the AccessToken caching and renewal and that provides a pre-authenticated `http.Client`. Epic: none Release note: None
1 parent 7d65e38 commit ba62711

File tree

14 files changed

+477
-460
lines changed

14 files changed

+477
-460
lines changed

DEPS.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,16 @@ def go_deps():
11271127
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/bgentry/speakeasy/com_github_bgentry_speakeasy-v0.1.0.zip",
11281128
],
11291129
)
1130+
go_repository(
1131+
name = "com_github_binxio_gcloudconfig",
1132+
build_file_proto_mode = "disable_global",
1133+
importpath = "github.com/binxio/gcloudconfig",
1134+
sha256 = "82797ef5d9fa4cba09d64ca885a3b6b8867d046c8f144ed15dc102085b0c6ceb",
1135+
strip_prefix = "github.com/binxio/[email protected]",
1136+
urls = [
1137+
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/binxio/gcloudconfig/com_github_binxio_gcloudconfig-v0.1.5.zip",
1138+
],
1139+
)
11301140
go_repository(
11311141
name = "com_github_biogo_store",
11321142
build_file_proto_mode = "disable_global",

build/bazelutil/distdir_files.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ DISTDIR_FILES = {
288288
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/beorn7/perks/com_github_beorn7_perks-v1.0.1.zip": "25bd9e2d94aca770e6dbc1f53725f84f6af4432f631d35dd2c46f96ef0512f1a",
289289
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/bgentry/go-netrc/com_github_bgentry_go_netrc-v0.0.0-20140422174119-9fd32a8b3d3d.zip": "59fbb1e8e307ccd7052f77186990d744284b186e8b1c5ebdfb12405ae8d7f935",
290290
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/bgentry/speakeasy/com_github_bgentry_speakeasy-v0.1.0.zip": "d4bfd48b9bf68c87f92c94478ac910bcdab272e15eb909d58f1fb939233f75f0",
291+
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/binxio/gcloudconfig/com_github_binxio_gcloudconfig-v0.1.5.zip": "82797ef5d9fa4cba09d64ca885a3b6b8867d046c8f144ed15dc102085b0c6ceb",
291292
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/biogo/store/com_github_biogo_store-v0.0.0-20160505134755-913427a1d5e8.zip": "26551f8829c5ada84a68ef240732375be6747252aba423cf5c88bc03002c3600",
292293
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/bitly/go-hostpool/com_github_bitly_go_hostpool-v0.0.0-20171023180738-a3a6125de932.zip": "9a55584d7fa2c1639d0ea11cd5b437786c2eadc2401d825e699ad6445fc8e476",
293294
"https://storage.googleapis.com/cockroach-godeps/gomod/github.com/bitly/go-simplejson/com_github_bitly_go_simplejson-v0.5.0.zip": "53930281dc7fba8947c1b1f07c82952a38dcaefae23bd3c8e71d70a6daa6cb40",

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ require (
124124
github.com/aws/smithy-go v1.22.3
125125
github.com/axiomhq/hyperloglog v0.2.5
126126
github.com/bazelbuild/rules_go v0.26.0
127+
github.com/binxio/gcloudconfig v0.1.5
127128
github.com/biogo/store v0.0.0-20160505134755-913427a1d5e8
128129
github.com/blevesearch/snowballstem v0.9.0
129130
github.com/buchgr/bazel-remote v1.3.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
450450
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
451451
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
452452
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
453+
github.com/binxio/gcloudconfig v0.1.5 h1:nbvWtpqn7yJs4qPuXxTu9D3DYrSyc0FHkXraseMMCV4=
454+
github.com/binxio/gcloudconfig v0.1.5/go.mod h1:IpQXzgqmv2JS1i+hbhqhHqzeYWg5zWkdN4sZJznJDUM=
453455
github.com/biogo/store v0.0.0-20160505134755-913427a1d5e8 h1:tYoz1OeRpx3dJZlh9T4dQt4kAndcmpl+VNdzbSgFC/0=
454456
github.com/biogo/store v0.0.0-20160505134755-913427a1d5e8/go.mod h1:Iev9Q3MErcn+w3UOJD/DkEzllvugfdx7bGcMOFhvr/4=
455457
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=

pkg/cmd/roachprod/cli/commands.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,9 +1723,9 @@ func (cr *commandRegistry) buildGrafanaAnnotationCmd() *cobra.Command {
17231723
Long: fmt.Sprintf(`Adds an annotation to the specified grafana instance
17241724
17251725
By default, we assume the grafana instance needs an authentication token to connect
1726-
to. A service account json and audience will be read in from the environment
1727-
variables %s and %s to attempt authentication through google IDP. Use the --insecure
1728-
option when a token is not necessary.
1726+
to. Unless the %s environment variable exists, the default Google Application Credentials
1727+
will be used to derive an Access Token to authenticate against Google Identity-Aware Proxy.
1728+
Use the --insecure option when a token is not necessary.
17291729
17301730
--tags specifies the tags the annotation should have.
17311731
@@ -1740,7 +1740,7 @@ creates an annotation over time range.
17401740
Example:
17411741
# Create an annotation over time range 1-100 on the centralized grafana instance, which needs authentication.
17421742
roachprod grafana-annotation grafana.testeng.crdb.io example-annotation-event --tags my-cluster --tags test-run-1 --dashboard-uid overview --time-range 1,100
1743-
`, roachprodutil.ServiceAccountJson, roachprodutil.ServiceAccountAudience),
1743+
`, roachprodutil.CredentialsEnvironmentVariable),
17441744
Args: cobra.ExactArgs(2),
17451745
Run: Wrap(func(cmd *cobra.Command, args []string) error {
17461746
req := grafana.AddAnnotationRequest{

pkg/cmd/roachprod/grafana/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ go_library(
2222
importpath = "github.com/cockroachdb/cockroach/pkg/cmd/roachprod/grafana",
2323
visibility = ["//visibility:public"],
2424
deps = [
25+
"//pkg/roachprod/promhelperclient",
2526
"//pkg/roachprod/roachprodutil",
2627
"//pkg/util/httputil",
2728
"@com_github_cockroachdb_errors//:errors",
2829
"@com_github_go_openapi_strfmt//:strfmt",
2930
"@com_github_grafana_grafana_openapi_client_go//client",
3031
"@com_github_grafana_grafana_openapi_client_go//models",
31-
"@org_golang_google_api//idtoken",
3232
],
3333
)

pkg/cmd/roachprod/grafana/annotations.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ package grafana
77

88
import (
99
"context"
10-
"fmt"
1110
"strings"
1211

12+
"github.com/cockroachdb/cockroach/pkg/roachprod/promhelperclient"
1313
"github.com/cockroachdb/cockroach/pkg/roachprod/roachprodutil"
1414
"github.com/cockroachdb/cockroach/pkg/util/httputil"
1515
"github.com/cockroachdb/errors"
1616
"github.com/go-openapi/strfmt"
1717
grafana "github.com/grafana/grafana-openapi-client-go/client"
1818
"github.com/grafana/grafana-openapi-client-go/models"
19-
"google.golang.org/api/idtoken"
2019
)
2120

2221
// newGrafanaClient is a helper function that creates an HTTP client to
@@ -26,30 +25,37 @@ import (
2625
func newGrafanaClient(
2726
ctx context.Context, host string, secure bool,
2827
) (*grafana.GrafanaHTTPAPI, error) {
29-
headers := map[string]string{}
3028
scheme := "http"
3129

30+
// Use the default HTTP client for unsecure Grafana calls.
31+
grafanaHttpClient := httputil.DefaultClient.Client
32+
3233
if secure {
3334
scheme = "https"
3435

35-
// Read in the service account key and audience, so we can retrieve the identity token.
36-
if _, err := roachprodutil.SetServiceAccountCredsEnv(ctx, false); err != nil {
37-
return nil, err
38-
}
39-
40-
token, err := roachprodutil.GetServiceAccountToken(ctx, idtoken.NewTokenSource)
36+
// Grafana annotations currently use the same service account
37+
// and OAuth client ID as the prometheus helper service.
38+
iapTokenSource, err := roachprodutil.NewIAPTokenSource(roachprodutil.IAPTokenSourceOptions{
39+
OAuthClientID: promhelperclient.OAuthClientID,
40+
ServiceAccountEmail: promhelperclient.ServiceAccountEmail,
41+
})
4142
if err != nil {
4243
return nil, err
4344
}
44-
headers["Authorization"] = fmt.Sprintf("Bearer %s", token)
45+
46+
// Override the default HTTP client with the one
47+
// that has the IAP token source.
48+
grafanaHttpClient = iapTokenSource.GetHTTPClient()
4549
}
4650

47-
headers[httputil.ContentTypeHeader] = httputil.JSONContentType
4851
cfg := &grafana.TransportConfig{
49-
Host: host,
50-
BasePath: "/api",
51-
Schemes: []string{scheme},
52-
HTTPHeaders: headers,
52+
Host: host,
53+
BasePath: "/api",
54+
Schemes: []string{scheme},
55+
HTTPHeaders: map[string]string{
56+
httputil.ContentTypeHeader: httputil.JSONContentType,
57+
},
58+
Client: grafanaHttpClient,
5359
}
5460

5561
return grafana.NewHTTPClientWithConfig(strfmt.Default, cfg), nil

pkg/roachprod/cloud/cluster_cloud.go

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,14 @@ func ShrinkCluster(l *logger.Logger, c *Cluster, numNodes int) error {
487487

488488
func (c *Cluster) DeletePrometheusConfig(ctx context.Context, l *logger.Logger) error {
489489

490-
cl := promhelperclient.NewPromClient()
491-
492490
stopSpinner := ui.NewDefaultSpinner(l, "Destroying Prometheus configs").Start()
493491
defer stopSpinner()
494492

493+
// We first iterate on all VMs to determine if any machine of the cluster
494+
// was reachable by Prometheus and if we need to delete its config.
495+
// This is done this way to avoid authenticating the promhelper client
496+
// in case we don't need to delete any config.
497+
needDelete := false
495498
for _, node := range c.VMs {
496499

497500
reachability := promhelperclient.ProviderReachability(
@@ -502,29 +505,20 @@ func (c *Cluster) DeletePrometheusConfig(ctx context.Context, l *logger.Logger)
502505
continue
503506
}
504507

505-
err := cl.DeleteClusterConfig(ctx, c.Name, false, false /* insecure */, l)
506-
if err != nil {
507-
508-
if !promhelperclient.IsNotFoundError(err) {
509-
return errors.Wrapf(
510-
err,
511-
"failed to delete the cluster config with cluster as secure",
512-
)
513-
}
514-
515-
// TODO(bhaskar): Obtain secure cluster information.
516-
// Cluster does not have the information on secure or not.
517-
// So, we retry as insecure if delete fails with cluster as secure.
518-
if err = cl.DeleteClusterConfig(ctx, c.Name, false, true /* insecure */, l); err != nil {
519-
return errors.Wrapf(
520-
err,
521-
"failed to delete the cluster config with cluster as insecure and secure",
522-
)
523-
}
508+
needDelete = true
509+
break
510+
}
524511

512+
if needDelete {
513+
cl, err := promhelperclient.NewPromClient()
514+
if err != nil {
515+
return err
525516
}
526-
break
527517

518+
err = cl.DeleteClusterConfig(ctx, c.Name, l)
519+
if err != nil {
520+
return err
521+
}
528522
}
529523

530524
return nil

pkg/roachprod/promhelperclient/BUILD.bazel

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ go_library(
1616
"//pkg/util/httputil",
1717
"@com_github_cockroachdb_errors//:errors",
1818
"@in_gopkg_yaml_v2//:yaml_v2",
19-
"@org_golang_google_api//idtoken",
20-
"@org_golang_x_oauth2//:oauth2",
2119
],
2220
)
2321

@@ -27,10 +25,8 @@ go_test(
2725
embed = [":promhelperclient"],
2826
deps = [
2927
"//pkg/roachprod/logger",
30-
"//pkg/roachprod/roachprodutil",
3128
"@com_github_stretchr_testify//require",
3229
"@in_gopkg_yaml_v2//:yaml_v2",
33-
"@org_golang_google_api//idtoken",
3430
"@org_golang_x_oauth2//:oauth2",
3531
],
3632
)

0 commit comments

Comments
 (0)