Skip to content

GODRIVER-2806 [master] Implement automatic GCP token acquisition (#1708) #1738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,31 @@ tasks:
export AZUREOIDC_TEST_CMD="PROJECT_DIRECTORY='.' OIDC_ENV=azure OIDC=oidc ./etc/run-oidc-test.sh ./test"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh

- name: "oidc-auth-test-gcp-latest"
commands:
- command: shell.exec
params:
working_dir: src/go.mongodb.org/mongo-driver
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-go-driver.tar.gz
# we need to statically link libc to avoid the situation where the VM has a different
# version of libc
go build -tags osusergo,netgo -ldflags '-w -extldflags "-static -lgcc -lc"' -o test ./internal/cmd/testoidcauth/main.go
rm "$GCPOIDC_DRIVERS_TAR_FILE" || true
tar -cf $GCPOIDC_DRIVERS_TAR_FILE ./test
tar -uf $GCPOIDC_DRIVERS_TAR_FILE ./etc
rm "$GCPOIDC_DRIVERS_TAR_FILE".gz || true
gzip $GCPOIDC_DRIVERS_TAR_FILE
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-go-driver.tar.gz
# Define the command to run on the gcp VM.
# Ensure that we source the environment file created for us, set up any other variables we need,
# and then run our test suite on the vm.
export GCPOIDC_TEST_CMD="PROJECT_DIRECTORY='.' OIDC_ENV=gcp OIDC=oidc ./etc/run-oidc-test.sh ./test"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh

- name: "test-search-index"
commands:
- func: "bootstrap-mongo-orchestration"
Expand Down Expand Up @@ -2326,6 +2351,30 @@ task_groups:
tasks:
- oidc-auth-test-azure-latest

- name: testgcpoidc_task_group
setup_group:
- func: fetch-source
- func: prepare-resources
- func: fix-absolute-paths
- func: make-files-executable
- command: subprocess.exec
params:
binary: bash
env:
AZUREOIDC_VMNAME_PREFIX: "GO_DRIVER"
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh
teardown_task:
- command: subprocess.exec
params:
binary: bash
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800
tasks:
- oidc-auth-test-gcp-latest

- name: test-aws-lambda-task-group
setup_group:
- func: fetch-source
Expand Down Expand Up @@ -2671,3 +2720,5 @@ buildvariants:
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
- name: testazureoidc_task_group
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
- name: testgcpoidc_task_group
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
19 changes: 19 additions & 0 deletions internal/cmd/testoidcauth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ func main() {
case "azure":
aux("machine_5_1_azureWithNoUsername", machine51azureWithNoUsername)
aux("machine_5_2_azureWithNoUsername", machine52azureWithBadUsername)
case "gcp":
aux("machine_6_1_gcpWithNoUsername", machine61gcpWithNoUsername)
default:
log.Fatal("Unknown OIDC_ENV: ", env)
}
Expand Down Expand Up @@ -745,3 +747,20 @@ func machine52azureWithBadUsername() error {
}
return nil
}

func machine61gcpWithNoUsername() error {
opts := options.Client().ApplyURI(uriSingle)
client, err := mongo.Connect(opts)
if err != nil {
return fmt.Errorf("machine_6_1: failed connecting client: %v", err)
}
defer func() { _ = client.Disconnect(context.Background()) }()

coll := client.Database("test").Collection("test")

_, err = coll.Find(context.Background(), bson.D{})
if err != nil {
return fmt.Errorf("machine_6_1: failed executing Find: %v", err)
}
return nil
}
42 changes: 37 additions & 5 deletions x/mongo/driver/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -175,12 +176,12 @@ func (oa *OIDCAuthenticator) providerCallback() (OIDCCallback, error) {
return nil, newAuthError(fmt.Sprintf("%q must be specified for Azure OIDC", resourceProp), nil)
}
return getAzureOIDCCallback(oa.userName, resource, oa.httpClient), nil
// TODO GODRIVER-2806: Automatic token acquisition for GCP Identity Provider
// This is here just to pass the linter, it will be fixed in one of the above tickets.
case gcpEnvironmentValue:
return func(ctx context.Context, args *OIDCArgs) (*OIDCCredential, error) {
return nil, fmt.Errorf("automatic token acquisition for %q not implemented yet", env)
}, fmt.Errorf("automatic token acquisition for %q not implemented yet", env)
resource, ok := oa.AuthMechanismProperties[resourceProp]
if !ok {
return nil, newAuthError(fmt.Sprintf("%q must be specified for GCP OIDC", resourceProp), nil)
}
return getGCPOIDCCallback(resource, oa.httpClient), nil
}

return nil, fmt.Errorf("%q %q not supported for MONGODB-OIDC", environmentProp, env)
Expand Down Expand Up @@ -229,6 +230,37 @@ func getAzureOIDCCallback(clientID string, resource string, httpClient *http.Cli
}
}

// getGCPOIDCCallback returns the callback for the GCP Identity Provider.
func getGCPOIDCCallback(resource string, httpClient *http.Client) OIDCCallback {
// return the callback parameterized by the clientID and resource, also passing in the user
// configured httpClient.
return func(ctx context.Context, args *OIDCArgs) (*OIDCCredential, error) {
resource = url.QueryEscape(resource)
uri := fmt.Sprintf("http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=%s", resource)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, newAuthError("error creating http request to GCP Identity Provider", err)
}
req.Header.Add("Metadata-Flavor", "Google")
resp, err := httpClient.Do(req)
if err != nil {
return nil, newAuthError("error getting access token from GCP Identity Provider", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, newAuthError(fmt.Sprintf("failed to get a valid response from GCP Identity Provider, http code: %d", resp.StatusCode), nil)
}
accessToken, err := io.ReadAll(resp.Body)
if err != nil {
return nil, newAuthError("failed parsing reading response from GCP Identity Provider", err)
}
return &OIDCCredential{
AccessToken: string(accessToken),
ExpiresAt: nil,
}, nil
}
}

func (oa *OIDCAuthenticator) getAccessToken(
ctx context.Context,
conn *mnet.Connection,
Expand Down
Loading