In Amazon SageMaker Unified Studio, the managed Tooling blueprint provisions a fixed set of resources for every
project. When your organization requires a different foundation, you can use a custom
blueprint as a project's Tooling environment instead. Assign the TOOLING
category when you create a custom blueprint, or update an existing custom blueprint to
comply with the minimum Tooling requirements in Template requirements. Amazon SageMaker Unified Studio accepts custom blueprints
categorized as TOOLING as the first environment deployed in a project
profile.
For information that applies to all custom blueprints, including the reserved parameters
that Amazon SageMaker Unified Studio populates and the trust policy that the provisioning role requires, see Custom blueprints.
When a blueprint is categorized as TOOLING, Amazon SageMaker Unified Studio validates your
template by calling cloudformation:GetTemplateSummary. The summary of your
template must adhere to the minimum Tooling requirements captured in this section,
including required AWS CloudFormation parameters, required AWS CloudFormation
resources, and required AWS CloudFormation exports.
Amazon SageMaker Unified Studio requires your template to declare the following three parameters so that
it can supply their values at deployment time.
datazoneEnvironmentDomainId
-
The domain ID.
datazoneEnvironmentEnvironmentId
-
The environment ID.
datazoneEnvironmentProjectId
-
The project ID.
DefaultIAMConnection
-
The IAM connection that tools use to authenticate. Its name
determines which project experience your users see. For more information
about project permission sets, see Tooling blueprints in Amazon SageMaker Unified Studio.
DefaultS3Connection
-
The project's primary Amazon S3 connection.
DefaultS3SharedConnection
-
The project's shared Amazon S3 connection, at the /shared/
prefix of project storage.
s3BucketArn
-
The ARN of the project's Amazon S3 bucket. A key prefix after the bucket
name is allowed.
s3BucketPath
-
The s3:// URI that other environments in the project
resolve their own paths against. A trailing forward slash is
optional.
Suffix each export name with the project ID and the scope name, which keeps the
name unique within an AWS account and AWS Region and matches the name that other
blueprints import. Declare datazoneScopeName with a default of
dev.
Outputs:
S3BucketArn:
Value: !Sub '${ProjectBucket.Arn}/'
Export:
Name: !Sub 's3BucketArn-${datazoneEnvironmentProjectId}-${datazoneScopeName}'
S3BucketPath:
Value: !Sub 's3://${ProjectBucket}/shared'
Export:
Name: !Sub 's3BucketPath-${datazoneEnvironmentProjectId}-${datazoneScopeName}'
The AWS CloudFormation export userRoleArn is not allowed.
Amazon SageMaker Unified Studio resolves the project role itself, from the RoleArn you
bind on the IAM connection, and rejects a template that exports the role
again.
Use one of the templates below to create a minimum Tooling template. Choose the
template that matches your domain type, and add capabilities from Tooling capabilities.
The name of the default IAM connection sets the project's experience. The name
must match the permission set that project members authenticate with:
default.iam for IAM permissions, or project.iam for
IAM Identity Center permissions. No other name is accepted. For more information
about project permission sets, see Tooling blueprints in Amazon SageMaker Unified Studio.
The CreateEnvironmentBlueprint API requires an Amazon S3 URL for the AWS
CloudFormation template. The blueprint's provisioning role must have access to that Amazon S3
object. The SageMakerStudioProjectProvisioningRolePolicy managed policy
grants s3:GetObject on buckets whose names begin with
amazon-sagemaker-cf-templates. The console creates such a bucket when
you upload a template there.
Register the template as a blueprint with
CreateEnvironmentBlueprint, passing TOOLING as the
blueprintCategory. Optionally, you can declare additional CloudFormation
parameters mapped to user parameters when you create a blueprint.
aws datazone create-environment-blueprint \
--domain-identifier domain-id \
--name blueprint-name \
--blueprint-category TOOLING \
--provisioning-properties '{
"cloudFormation": {
"templateUrl": "https://amazon-sagemaker-cf-templates-region-suffix.s3.region.amazonaws.com/tooling-template.yaml"
}
}' \
--user-parameters '[
{
"keyName": "key",
"fieldType": "String",
"defaultValue": "value",
"isEditable": true,
"isOptional": true
}
]'
By default, the blueprint is disabled after it is created. To enable the blueprint in
an AWS account and AWS Region, you must provide a provisioning role.
Amazon SageMaker Unified Studio deploys your template under the blueprint's provisioning role, so that
role's trust policy must let Amazon SageMaker Unified Studio assume it with sts:AssumeRole and
pass your user context with sts:TagSession for the
datazone.amazonaws.com service principal. For the policy, see Trust policy for the provisioning role.
aws datazone put-environment-blueprint-configuration \
--domain-identifier domain-id \
--environment-blueprint-identifier blueprint-id \
--enabled-regions region \
--provisioning-role-arn provisioning-role-arn \
--regional-parameters '{
"region": {
"kmsKeyArn": "kms-key-arn"
}
}'
- IAM-based domain
-
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
datazoneEnvironmentDomainId: { Type: String }
datazoneEnvironmentEnvironmentId: { Type: String }
datazoneEnvironmentProjectExecutionRoleArn: { Type: String }
datazoneEnvironmentProjectId: { Type: String }
datazoneScopeName: { Type: String, Default: 'dev' }
sagemakerUnifiedStudioKmsKeyArn: { Type: String, Default: '' }
Conditions:
kmsKeyArnExist: !Not [!Equals [!Ref sagemakerUnifiedStudioKmsKeyArn, '']]
Resources:
ProjectBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
BucketName: !Sub 'amazon-sagemaker-${AWS::AccountId}-${AWS::Region}-${datazoneEnvironmentProjectId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: !If [kmsKeyArnExist, 'aws:kms', 'AES256']
KMSMasterKeyID: !If [kmsKeyArnExist, !Ref sagemakerUnifiedStudioKmsKeyArn, !Ref 'AWS::NoValue']
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [PUT, GET, POST, DELETE, HEAD]
AllowedOrigins: ['*']
ExposedHeaders: [x-amz-version-id, ETag, x-amz-checksum-crc32]
ProjectBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ProjectBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: EnforceSSL
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource: [!GetAtt ProjectBucket.Arn, !Sub '${ProjectBucket.Arn}/*']
Condition: { Bool: { 'aws:SecureTransport': 'false' } }
- Sid: ProjectUserRoleAccess
Effect: Allow
Principal: '*'
Action:
- s3:GetObject*
- s3:GetBucket*
- s3:PutObject*
- s3:ListBucket*
- s3:RestoreObject
- s3:ReplicateObject
- s3:DeleteObject
- s3:DeleteObjectVersion
- s3:ListMultipartUploadParts
- s3:AbortMultipartUpload
- s3:GetEncryptionConfiguration
Resource: [!GetAtt ProjectBucket.Arn, !Sub '${ProjectBucket.Arn}/*']
Condition: { ArnEquals: { 'aws:PrincipalArn': !Ref datazoneEnvironmentProjectExecutionRoleArn } }
- Sid: DenyDeleteOfShared
Effect: Deny
Principal: '*'
Action: s3:DeleteObject
Resource:
- !Sub '${ProjectBucket.Arn}/shared/'
- !Sub '${ProjectBucket.Arn}/shared/*'
Condition: { ArnEquals: { 'aws:PrincipalArn': !Ref datazoneEnvironmentProjectExecutionRoleArn } }
DefaultIAMConnection:
Type: AWS::DataZone::Connection
Properties:
Name: default.iam
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation: { AwsAccountId: !Ref 'AWS::AccountId', AwsRegion: !Ref 'AWS::Region' }
Props:
IamProperties:
GlueLineageSyncEnabled: true
RoleArn: !Ref datazoneEnvironmentProjectExecutionRoleArn
DefaultS3Connection:
Type: AWS::DataZone::Connection
Properties:
Name: default.s3
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation:
AwsAccountId: !Ref 'AWS::AccountId'
AwsRegion: !Ref 'AWS::Region'
IamConnectionId: !GetAtt DefaultIAMConnection.ConnectionId
Props:
S3Properties: { S3Uri: !Sub 's3://${ProjectBucket}/', RegisterS3AccessGrantLocation: false }
DefaultS3SharedConnection:
Type: AWS::DataZone::Connection
Properties:
Name: default.s3_shared
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation:
AwsAccountId: !Ref 'AWS::AccountId'
AwsRegion: !Ref 'AWS::Region'
IamConnectionId: !GetAtt DefaultIAMConnection.ConnectionId
Props:
S3Properties: { S3Uri: !Sub 's3://${ProjectBucket}/shared/', RegisterS3AccessGrantLocation: false }
Outputs:
S3BucketArn:
Value: !Sub '${ProjectBucket.Arn}/'
Export: { Name: !Sub 's3BucketArn-${datazoneEnvironmentProjectId}-${datazoneScopeName}' }
S3BucketPath:
Value: !Sub 's3://${ProjectBucket}/shared'
Export: { Name: !Sub 's3BucketPath-${datazoneEnvironmentProjectId}-${datazoneScopeName}' }
- Identity Center-based domain
-
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
awsDefaultGlueCatalogKmsKeyArn: { Type: String, Default: '' }
datazoneEnvironmentDomainArn: { Type: String }
datazoneEnvironmentDomainId: { Type: String }
datazoneEnvironmentDomainKmsKeyArn: { Type: String, Default: '' }
datazoneEnvironmentEnvironmentId: { Type: String }
datazoneEnvironmentProjectId: { Type: String }
datazoneEnvironmentProvisioningRoleArn: { Type: String }
datazoneScopeName: { Type: String, Default: 'dev' }
sagemakerUnifiedStudioKmsKeyArn: { Type: String, Default: '' }
sagemakerUnifiedStudioNetworkVpcId: { Type: String, Default: '' }
Conditions:
defaultGlueCatalogKmsKeyArnExist: !Not [!Equals [!Ref awsDefaultGlueCatalogKmsKeyArn, '']]
domainKmsKeyArnExist: !Not [!Equals [!Ref datazoneEnvironmentDomainKmsKeyArn, '']]
kmsKeyArnExist: !Not [!Equals [!Ref sagemakerUnifiedStudioKmsKeyArn, '']]
Resources:
ProjectBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
BucketName: !Sub 'sagemaker-${AWS::AccountId}-${AWS::Region}-${datazoneEnvironmentProjectId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: !If [kmsKeyArnExist, 'aws:kms', 'AES256']
KMSMasterKeyID: !If [kmsKeyArnExist, !Ref sagemakerUnifiedStudioKmsKeyArn, !Ref 'AWS::NoValue']
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [PUT, GET, POST, DELETE, HEAD]
AllowedOrigins: ['*']
ExposedHeaders: [x-amz-version-id, ETag, x-amz-checksum-crc32]
ToolingLogGroup:
Type: AWS::Logs::LogGroup
DeletionPolicy: Delete
Properties:
LogGroupName: !Sub 'datazone-${datazoneEnvironmentProjectId}-${datazoneScopeName}'
RetentionInDays: 30
ProjectUserRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'datazone_usr_role_${datazoneEnvironmentProjectId}_${datazoneEnvironmentEnvironmentId}'
Description: User role for all environments and tools.
Path: /
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/SageMakerStudioProjectUserRolePolicy'
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/SageMakerStudioProjectRoleMachineLearningPolicy'
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/SageMakerStudioBedrockKnowledgeBaseServiceRolePolicy'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: [datazone.amazonaws.com, auth.datazone.amazonaws.com]
Action: [sts:AssumeRole, sts:TagSession, sts:SetContext, sts:SetSourceIdentity]
Condition:
StringEquals:
aws:SourceAccount: !Select [4, !Split [':', !Ref datazoneEnvironmentDomainArn]]
- Effect: Allow
Principal:
Service:
- glue.amazonaws.com
- lakeformation.amazonaws.com
- sagemaker.amazonaws.com
- emr-serverless.amazonaws.com
- elasticmapreduce.amazonaws.com
- airflow.amazonaws.com
- airflow-env.amazonaws.com
- lambda.amazonaws.com
- scheduler.amazonaws.com
- quicksight.amazonaws.com
- access-grants.s3.amazonaws.com
- athena.amazonaws.com
- airflow-serverless.amazonaws.com
- bedrock.amazonaws.com
Action: [sts:AssumeRole, sts:TagSession, sts:SetContext, sts:SetSourceIdentity]
Condition:
StringEquals: { aws:SourceAccount: !Ref 'AWS::AccountId' }
- Effect: Allow
Principal:
Service: [redshift-serverless.amazonaws.com, redshift.amazonaws.com]
Action: sts:AssumeRole
Condition:
StringLike:
sts:ExternalId: !Sub 'arn:${AWS::Partition}:redshift:*:${AWS::AccountId}:dbuser:*/*'
- Effect: Allow
Principal:
Service: [redshift-serverless.amazonaws.com, redshift.amazonaws.com]
Action: [sts:TagSession, sts:SetContext, sts:SetSourceIdentity]
- Effect: Allow
Principal:
AWS: !Ref datazoneEnvironmentProvisioningRoleArn
Action: [sts:AssumeRole, sts:TagSession, sts:SetContext, sts:SetSourceIdentity]
Tags:
- { Key: AmazonDataZoneScopeName, Value: !Ref datazoneScopeName }
- { Key: RedshiftDbUser, Value: !Sub 'user-project@${datazoneEnvironmentProjectId}' }
- { Key: DomainBucketName, Value: !Ref ProjectBucket }
- { Key: EnableAmazonBedrockPermissions, Value: 'true' }
- { Key: EnableGlueWorkloadsPermissions, Value: 'true' }
- { Key: EnableSageMakerMLWorkloadsPermissions, Value: 'true' }
- { Key: KmsKeyId, Value: !If [kmsKeyArnExist, !Select [1, !Split ['/', !Ref sagemakerUnifiedStudioKmsKeyArn]], ''] }
- { Key: DomainKmsKeyId, Value: !If [domainKmsKeyArnExist, !Select [1, !Split ['/', !Ref datazoneEnvironmentDomainKmsKeyArn]], ''] }
- { Key: DefaultGlueCatalogKmsKeyId, Value: !If [defaultGlueCatalogKmsKeyArnExist, !Select [1, !Split ['/', !Ref awsDefaultGlueCatalogKmsKeyArn]], ''] }
- { Key: LogGroupName, Value: !Ref ToolingLogGroup }
- { Key: RoleName, Value: !Sub 'datazone_usr_role_${datazoneEnvironmentProjectId}_${datazoneEnvironmentEnvironmentId}' }
- { Key: vpcArn, Value: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${sagemakerUnifiedStudioNetworkVpcId}' }
- { Key: VpcId, Value: !Ref sagemakerUnifiedStudioNetworkVpcId }
ProjectBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ProjectBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: DenyInsecureConnections
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource: [!GetAtt ProjectBucket.Arn, !Sub '${ProjectBucket.Arn}/*']
Condition: { Bool: { 'aws:SecureTransport': 'false' } }
- Sid: AllowToolingUserAccess
Effect: Allow
Principal: '*'
Action: [s3:GetObject*, s3:GetBucket*, s3:PutObject*, s3:ListBucket*, s3:RestoreObject, s3:ReplicateObject,
s3:DeleteObject, s3:DeleteObjectVersion, s3:ListMultipartUploadParts, s3:AbortMultipartUpload,
s3:GetEncryptionConfiguration]
Resource: [!GetAtt ProjectBucket.Arn, !Sub '${ProjectBucket.Arn}/*']
Condition: { ArnEquals: { 'aws:PrincipalArn': !GetAtt ProjectUserRole.Arn } }
DefaultIAMConnection:
Type: AWS::DataZone::Connection
Properties:
Name: project.iam
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation: { AwsAccountId: !Ref 'AWS::AccountId', AwsRegion: !Ref 'AWS::Region' }
Props:
IamProperties:
GlueLineageSyncEnabled: true
RoleArn: !GetAtt ProjectUserRole.Arn
DefaultS3Connection:
Type: AWS::DataZone::Connection
Properties:
Name: default.s3
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation:
AwsAccountId: !Ref 'AWS::AccountId'
AwsRegion: !Ref 'AWS::Region'
IamConnectionId: !GetAtt DefaultIAMConnection.ConnectionId
Props:
S3Properties: { S3Uri: !Sub 's3://${ProjectBucket}/', RegisterS3AccessGrantLocation: false }
DefaultS3SharedConnection:
Type: AWS::DataZone::Connection
Properties:
Name: default.s3_shared
DomainIdentifier: !Ref datazoneEnvironmentDomainId
EnvironmentIdentifier: !Ref datazoneEnvironmentEnvironmentId
ProjectIdentifier: !Ref datazoneEnvironmentProjectId
AwsLocation:
AwsAccountId: !Ref 'AWS::AccountId'
AwsRegion: !Ref 'AWS::Region'
IamConnectionId: !GetAtt DefaultIAMConnection.ConnectionId
Props:
S3Properties: { S3Uri: !Sub 's3://${ProjectBucket}/shared/', RegisterS3AccessGrantLocation: false }
Outputs:
S3BucketArn:
Value: !Sub '${ProjectBucket.Arn}/'
Export: { Name: !Sub 's3BucketArn-${datazoneEnvironmentProjectId}-${datazoneScopeName}' }
S3BucketPath:
Value: !Sub 's3://${ProjectBucket}/shared'
Export: { Name: !Sub 's3BucketPath-${datazoneEnvironmentProjectId}-${datazoneScopeName}' }
After your blueprint is created and enabled, you use it by composing it into a project
profile and then creating a project from that profile.
A project profile names the blueprint that fills the Tooling slot and declares
which parameters a project creator can set. Call
CreateProjectProfile.
Give the Tooling environment a deploymentMode of
ON_CREATE and a deploymentOrder of 0, so
that it deploys before anything else in the profile.
aws datazone create-project-profile \
--domain-identifier domain-id \
--name profile-name \
--status ENABLED \
--environment-configurations '[
{
"name": "CustomTooling",
"environmentBlueprintId": "blueprint-id",
"deploymentMode": "ON_CREATE",
"deploymentOrder": 0,
"awsAccount": { "awsAccountId": "account-id" },
"awsRegion": { "regionName": "region" },
"configurationParameters": {
"parameterOverrides": [
{ "name": "parameter-name", "value": "" }
]
}
}
]'
Pass --status ENABLED. A profile is created
DISABLED, and a disabled profile can't be used to create a
project.
Call CreateProject and reference the profile. Supply user
parameter values under --user-parameters. The
environmentConfigurationName value must match the name in the
profile exactly.
aws datazone create-project \
--domain-identifier domain-id \
--name project-name \
--project-profile-id profile-id \
--user-parameters '[
{
"environmentConfigurationName": "CustomTooling",
"environmentParameters": [
{ "name": "parameter-name", "value": "value" }
]
}
]'
In an IAM-based domain, also pass the role that becomes the project role.
Your template receives this ARN through the reserved parameter
datazoneEnvironmentProjectExecutionRoleArn. In an Identity
Center-based domain, omit it, because your template creates the project role
itself.
aws datazone create-project \
--domain-identifier domain-id \
--name project-name \
--project-profile-id profile-id \
--project-execution-role project-role-arn