Skip to content

Commit

Permalink
add GCP Workload Identity Federation Auth Strategy (#870)
Browse files Browse the repository at this point in the history
* Add GCP Workload Identity Federation Auth Strategy

* Add test for GCP WIF

* add changeset

* modify structure of auth strategy

* fix: gcp-wif auth select

* refactor gcp wif from aws specific to general

* fix merge changes

* fix lint error

* Refactor code - observable & action

* fix error in conn editor state

* fix error in editor state

* add manual test

* Remove changesets

* Add desired changesets, fix test

* fix broken test

* fix manual test

* fix manual test

* fix manual test
  • Loading branch information
abhishoya-gs authored May 25, 2022
1 parent 8885ce4 commit 8334cdc
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changeset/little-otters-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
'@finos/legend-manual-tests': patch
---
6 changes: 6 additions & 0 deletions .changeset/thin-emus-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@finos/legend-graph': patch
'@finos/legend-studio': patch
---

Add GCP Workload Identity Federation Authentication Strategy
1 change: 1 addition & 0 deletions packages/legend-graph/src/MetaModelConst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export enum CORE_HASH_STRUCTURE {
GCP_APPLICATION_DEFAULT_CREDENTIALS_AUTHENTICATION_STRATEGY = 'GCP_APPLICATION_DEFAULT_CREDENTIALS_AUTHENTICATION_STRATEGY',
USERNAME_PASSWORD_AUTHENTICATION_STRATEGY = 'USERNAME_PASSWORD_AUTHENTICATION_STRATEGY',
OAUTH_AUTHENTICATION_STRATEGY = 'OAUTH_AUTHENTICATION_STRATEGY',
GCP_WORKLOAD_IDENTITY_FEDERATION_AUTHENTICATION_STRATEGY = 'GCP_WORKLOAD_IDENTITY_FEDERATION_AUTHENTICATION_STRATEGY',
// relational database connection post processors
MAPPER_POST_PROCESSOR = 'MAPPER_POST_PROCESSOR',
SCHEMA_MAPPER = 'SCHEMA_MAPPER',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4047,7 +4047,7 @@ export const TEST_DATA__RelationalDatabaseConnectionRoundtrip = [
classifierPath: 'meta::pure::runtime::PackageableConnection',
},
{
path: 'apps::myBigQuery',
path: 'apps::myBigQueryWithApplicationDefaultCredentials',
content: {
_type: 'connection',
connectionValue: {
Expand All @@ -4066,7 +4066,34 @@ export const TEST_DATA__RelationalDatabaseConnectionRoundtrip = [
element: 'apps::pure::studio::relational::tests::dbInc',
type: 'BigQuery',
},
name: 'myBigQuery',
name: 'myBigQueryWithApplicationDefaultCredentials',
package: 'apps',
},
classifierPath: 'meta::pure::runtime::PackageableConnection',
},
{
path: 'apps::myBigQueryWithWorkloadIdentityFederation',
content: {
_type: 'connection',
connectionValue: {
_type: 'RelationalDatabaseConnection',
authenticationStrategy: {
_type: 'gcpWorkloadIdentityFederation',
additionalGcpScopes: [],
serviceAccountEmail: 'serviceAccountEmail',
},
databaseType: 'BigQuery',
datasourceSpecification: {
_type: 'bigQuery',
defaultDataset: 'legend_testing_dataset',
projectId: 'legend-integration-testing',
proxyHost: 'proxy-host',
proxyPort: '8080',
},
element: 'apps::pure::studio::relational::tests::dbInc',
type: 'BigQuery',
},
name: 'myBigQueryWithWorkloadIdentityFederation',
package: 'apps',
},
classifierPath: 'meta::pure::runtime::PackageableConnection',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
OAuthAuthenticationStrategy,
SnowflakePublicAuthenticationStrategy,
UsernamePasswordAuthenticationStrategy,
GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../models/metamodels/pure/packageableElements/store/relational/connection/AuthenticationStrategy';
import {
type DatasourceSpecification,
Expand Down Expand Up @@ -926,6 +927,18 @@ export const observe_UsernamePasswordAuthenticationStrategy = skipObserved(
}),
);

export const observe_GCPWorkloadIdentityFederationAuthenticationStrategy =
skipObserved(
(
metamodel: GCPWorkloadIdentityFederationAuthenticationStrategy,
): GCPWorkloadIdentityFederationAuthenticationStrategy =>
makeObservable(metamodel, {
hashCode: computed,
serviceAccountEmail: observable,
additionalGcpScopes: observable,
}),
);

export const observe_AuthenticationStrategy = (
metamodel: AuthenticationStrategy,
context: ObserverContext,
Expand All @@ -948,6 +961,12 @@ export const observe_AuthenticationStrategy = (
);
} else if (metamodel instanceof UsernamePasswordAuthenticationStrategy) {
return observe_UsernamePasswordAuthenticationStrategy(metamodel);
} else if (
metamodel instanceof GCPWorkloadIdentityFederationAuthenticationStrategy
) {
return observe_GCPWorkloadIdentityFederationAuthenticationStrategy(
metamodel,
);
}
const extraObservers = context.plugins.flatMap(
(plugin) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,28 @@ export class GCPApplicationDefaultCredentialsAuthenticationStrategy
}
}

export class GCPWorkloadIdentityFederationAuthenticationStrategy
extends AuthenticationStrategy
implements Hashable
{
serviceAccountEmail: string;
additionalGcpScopes: string[] = [];

constructor(serviceAccountEmail: string, additionalGcpScopes: string[] = []) {
super();
this.serviceAccountEmail = serviceAccountEmail;
this.additionalGcpScopes = additionalGcpScopes;
}

get hashCode(): string {
return hashArray([
CORE_HASH_STRUCTURE.GCP_WORKLOAD_IDENTITY_FEDERATION_AUTHENTICATION_STRATEGY,
this.serviceAccountEmail,
hashArray(this.additionalGcpScopes),
]);
}
}

export class UsernamePasswordAuthenticationStrategy
extends AuthenticationStrategy
implements Hashable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ export class V1_GCPApplicationDefaultCredentialsAuthenticationStrategy
}
}

export class V1_GCPWorkloadIdentityFederationAuthenticationStrategy
extends V1_AuthenticationStrategy
implements Hashable
{
serviceAccountEmail!: string;
additionalGcpScopes: string[] = [];

get hashCode(): string {
return hashArray([
CORE_HASH_STRUCTURE.GCP_WORKLOAD_IDENTITY_FEDERATION_AUTHENTICATION_STRATEGY,
this.serviceAccountEmail,
hashArray(this.additionalGcpScopes),
]);
}
}

export class V1_UsernamePasswordAuthenticationStrategy
extends V1_AuthenticationStrategy
implements Hashable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
DelegatedKerberosAuthenticationStrategy,
OAuthAuthenticationStrategy,
UsernamePasswordAuthenticationStrategy,
GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../../../../metamodels/pure/packageableElements/store/relational/connection/AuthenticationStrategy';
import {
type DatasourceSpecification,
Expand Down Expand Up @@ -73,6 +74,7 @@ import {
V1_ApiTokenAuthenticationStrategy,
V1_DelegatedKerberosAuthenticationStrategy,
V1_OAuthAuthenticationStrategy,
V1_GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../model/packageableElements/store/relational/connection/V1_AuthenticationStrategy';
import type { V1_Connection } from '../../../model/packageableElements/connection/V1_Connection';
import {
Expand Down Expand Up @@ -241,6 +243,13 @@ const transformAuthenticationStrategy = (
const auth =
new V1_GCPApplicationDefaultCredentialsAuthenticationStrategy();
return auth;
} else if (
metamodel instanceof GCPWorkloadIdentityFederationAuthenticationStrategy
) {
const auth = new V1_GCPWorkloadIdentityFederationAuthenticationStrategy();
auth.serviceAccountEmail = metamodel.serviceAccountEmail;
auth.additionalGcpScopes = metamodel.additionalGcpScopes;
return auth;
} else if (metamodel instanceof UsernamePasswordAuthenticationStrategy) {
const auth = new V1_UsernamePasswordAuthenticationStrategy();
auth.baseVaultReference = metamodel.baseVaultReference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
OAuthAuthenticationStrategy,
DefaultH2AuthenticationStrategy,
DelegatedKerberosAuthenticationStrategy,
GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../../../../../metamodels/pure/packageableElements/store/relational/connection/AuthenticationStrategy';
import type { V1_GraphBuilderContext } from '../../../../transformation/pureGraph/to/V1_GraphBuilderContext';
import {
Expand All @@ -60,6 +61,7 @@ import {
V1_ApiTokenAuthenticationStrategy,
V1_DelegatedKerberosAuthenticationStrategy,
V1_UsernamePasswordAuthenticationStrategy,
V1_GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../../model/packageableElements/store/relational/connection/V1_AuthenticationStrategy';
import type { StoreRelational_PureProtocolProcessorPlugin_Extension } from '../../../../../StoreRelational_PureProtocolProcessorPlugin_Extension';

Expand Down Expand Up @@ -267,6 +269,18 @@ export const V1_buildAuthenticationStrategy = (
V1_GCPApplicationDefaultCredentialsAuthenticationStrategy
) {
return new GCPApplicationDefaultCredentialsAuthenticationStrategy();
} else if (
protocol instanceof V1_GCPWorkloadIdentityFederationAuthenticationStrategy
) {
assertNonEmptyString(
protocol.serviceAccountEmail,
`GCPWorkloadIdentityFederation 'serviceAccountEmail' field is missing or empty`,
);

return new GCPWorkloadIdentityFederationAuthenticationStrategy(
protocol.serviceAccountEmail,
protocol.additionalGcpScopes,
);
} else if (protocol instanceof V1_OAuthAuthenticationStrategy) {
return new OAuthAuthenticationStrategy(
guaranteeNonEmptyString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
V1_DefaultH2AuthenticationStrategy,
V1_DelegatedKerberosAuthenticationStrategy,
V1_UsernamePasswordAuthenticationStrategy,
V1_GCPWorkloadIdentityFederationAuthenticationStrategy,
} from '../../../model/packageableElements/store/relational/connection/V1_AuthenticationStrategy';
import type { PureProtocolProcessorPlugin } from '../../../../PureProtocolProcessorPlugin';
import type { StoreRelational_PureProtocolProcessorPlugin_Extension } from '../../../../StoreRelational_PureProtocolProcessorPlugin_Extension';
Expand Down Expand Up @@ -344,6 +345,7 @@ enum V1_AuthenticationStrategyType {
H2_DEFAULT = 'h2Default',
OAUTH = 'oauth',
USERNAME_PASSWORD = 'userNamePassword',
GCP_WORKLOAD_IDENTITY_FEDERATION = 'gcpWorkloadIdentityFederation',
}

const V1_delegatedKerberosAuthenticationStrategyModelSchema = createModelSchema(
Expand Down Expand Up @@ -388,6 +390,15 @@ const V1_GCPApplicationDefaultCredentialsAuthenticationStrategyModelSchema =
),
});

const V1_GCPWorkloadIdentityFederationAuthenticationStrategyModelSchema =
createModelSchema(V1_GCPWorkloadIdentityFederationAuthenticationStrategy, {
_type: usingConstantValueSchema(
V1_AuthenticationStrategyType.GCP_WORKLOAD_IDENTITY_FEDERATION,
),
additionalGcpScopes: list(primitive()),
serviceAccountEmail: primitive(),
});

const V1_UsernamePasswordAuthenticationStrategyModelSchema = createModelSchema(
V1_UsernamePasswordAuthenticationStrategy,
{
Expand Down Expand Up @@ -435,6 +446,13 @@ export const V1_serializeAuthenticationStrategy = (
V1_GCPApplicationDefaultCredentialsAuthenticationStrategyModelSchema,
protocol,
);
} else if (
protocol instanceof V1_GCPWorkloadIdentityFederationAuthenticationStrategy
) {
return serialize(
V1_GCPWorkloadIdentityFederationAuthenticationStrategyModelSchema,
protocol,
);
} else if (protocol instanceof V1_OAuthAuthenticationStrategy) {
return serialize(V1_oAuthAuthenticationStrategyModelSchema, protocol);
} else if (protocol instanceof V1_UsernamePasswordAuthenticationStrategy) {
Expand Down Expand Up @@ -487,6 +505,11 @@ export const V1_deserializeAuthenticationStrategy = (
V1_GCPApplicationDefaultCredentialsAuthenticationStrategyModelSchema,
json,
);
case V1_AuthenticationStrategyType.GCP_WORKLOAD_IDENTITY_FEDERATION:
return deserialize(
V1_GCPWorkloadIdentityFederationAuthenticationStrategyModelSchema,
json,
);
case V1_AuthenticationStrategyType.OAUTH:
return deserialize(V1_oAuthAuthenticationStrategyModelSchema, json);
case V1_AuthenticationStrategyType.USERNAME_PASSWORD:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,24 @@ RelationalDatabaseConnection connection::relational::StaticWithUserNameNoBase
};
}

RelationalDatabaseConnection connection::relational::BQWithGCPWorkloadIdentityFederation
{
store: store::db;
type: BigQuery;
specification: BigQuery
{
projectId: 'projectId';
defaultDataset: 'defaultDataset';
};
auth: GCPWorkloadIdentityFederation
{
serviceAccountEmail: '[email protected]';
additionalGcpScopes: [
'someScope'
];
};
}

RelationalDatabaseConnection connection::relational::BigQueryWithoutProxy
{
store: store::db;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
SnowflakePublicAuthenticationStrategy,
ApiTokenAuthenticationStrategy,
UsernamePasswordAuthenticationStrategy,
GCPWorkloadIdentityFederationAuthenticationStrategy,
EmbeddedH2DatasourceSpecification,
LocalH2DatasourceSpecification,
SnowflakeDatasourceSpecification,
Expand Down Expand Up @@ -111,6 +112,8 @@ import {
usernamePasswordAuthenticationStrategy_setBaseVaultReference,
usernamePasswordAuthenticationStrategy_setPasswordVaultReference,
usernamePasswordAuthenticationStrategy_setUserNameVaultReference,
gcpWorkloadIdentityFederationAuthenticationStrategy_setServiceAccountEmail,
gcpWorkloadIdentityFederationAuthenticationStrategy_setAdditionalGcpScopes,
} from '../../../../stores/graphModifier/StoreRelational_GraphModifierHelper';

/**
Expand Down Expand Up @@ -1012,6 +1015,42 @@ const UsernamePasswordAuthenticationStrategyEditor = observer(
},
);

const GCPWorkloadIdentityFederationAuthenticationStrategyEditor = observer(
(props: {
authSpec: GCPWorkloadIdentityFederationAuthenticationStrategy;
isReadOnly: boolean;
}) => {
const { authSpec, isReadOnly } = props;
const GCPScopes = authSpec.additionalGcpScopes.join('\n');
return (
<>
<ConnectionEditor_StringEditor
isReadOnly={isReadOnly}
value={authSpec.serviceAccountEmail}
propertyName={'Service Account Email'}
update={(value: string | undefined): void =>
gcpWorkloadIdentityFederationAuthenticationStrategy_setServiceAccountEmail(
authSpec,
value ?? '',
)
}
/>
<ConnectionEditor_StringEditor
isReadOnly={isReadOnly}
value={GCPScopes}
propertyName={'Additional GCP Scopes'}
update={(value: string | undefined): void =>
gcpWorkloadIdentityFederationAuthenticationStrategy_setAdditionalGcpScopes(
authSpec,
value ? [value] : [],
)
}
/>
</>
);
},
);

const RelationalConnectionStoreEditor = observer(
(props: {
connectionValueState: RelationalDatabaseConnectionValueState;
Expand Down Expand Up @@ -1199,6 +1238,15 @@ const renderAuthenticationStrategyEditor = (
isReadOnly={isReadOnly}
/>
);
} else if (
authSpec instanceof GCPWorkloadIdentityFederationAuthenticationStrategy
) {
return (
<GCPWorkloadIdentityFederationAuthenticationStrategyEditor
authSpec={authSpec}
isReadOnly={isReadOnly}
/>
);
} else {
const extraAuthenticationStrategyEditorRenderers = plugins.flatMap(
(plugin) =>
Expand Down
Loading

0 comments on commit 8334cdc

Please sign in to comment.