diff --git a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-cur-and-bucket.json b/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-cur-and-bucket.json deleted file mode 100644 index 7344bdd..0000000 --- a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-cur-and-bucket.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Outputs": { - "SpotFinOpsRoleArn": { - "Value": { - "Fn::GetAtt": [ - "SpotFinOpsRole", - "Arn" - ] - } - } - }, - "Parameters": { - "CostAndUsageBucket": { - "Type": "String", - "Description": "The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports" - }, - "RoleName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly" - }, - "PolicyName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly_Policy" - } - }, - "Resources": { - "SpotCurBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": { - "Fn::Sub": [ - "${CostAndUsageBucket}${RandomGUID}", - { - "RandomGUID": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] - } - } - }, - "CostAndUsageReport": { - "Type": "AWS::CUR::ReportDefinition", - "Properties": { - "ReportName": "spot-io-cur", - "TimeUnit": "HOURLY", - "Format": "Parquet", - "Compression": "Parquet", - "S3Bucket": { - "Ref": "CostAndUsageBucket" - }, - "S3Prefix": "cost-and-usage-reports/", - "S3Region": "us-east-1", - "AdditionalSchemaElements": [ - "RESOURCES" - ], - "AdditionalArtifacts": [ - "ATHENA" - ], - "RefreshClosedReports": true, - "ReportVersioning": "OVERWRITE_REPORT" - } - }, - "S3BucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "CostAndUsageBucket" - }, - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowCostAndUsageReportPut", - "Effect": "Allow", - "Principal": { - "Service": [ - "billingreports.amazonaws.com" - ] - }, - "Action": [ - "s3:PutObject" - ], - "Resource": { - "Fn::Sub": "arn:aws:s3:::${CostAndUsageBucket}/*" - } - }, - { - "Sid": "AllowCostAndUsageReport", - "Effect": "Allow", - "Principal": { - "Service": [ - "billingreports.amazonaws.com" - ] - }, - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy" - ], - "Resource": { - "Fn::Sub": "arn:aws:s3:::${CostAndUsageBucket}" - } - } - ] - } - } - }, - "SpotFinOpsManagedPolicy": { - "Type": "AWS::IAM::ManagedPolicy", - "Properties": { - "ManagedPolicyName": { - "Ref": "PolicyName" - }, - "Description": "Spot by NetApp Finops ReadOnly Policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "es:ListElasticsearchInstanceTypes", - "es:DescribeReservedElasticsearchInstanceOfferings", - "es:DescribeReservedElasticsearchInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticSearch" - }, - { - "Action": [ - "rds:DescribeReservedDBInstances", - "rds:DescribeDBInstances", - "rds:DescribeReservedDBInstancesOfferings" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRDS" - }, - { - "Action": [ - "redshift:DescribeReservedNodeOfferings", - "redshift:DescribeReservedNodes", - "redshift:DescribeClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRedshift" - }, - { - "Action": [ - "elasticache:DescribeReservedCacheNodesOfferings", - "elasticache:DescribeReservedCacheNodes", - "elasticache:DescribeCacheClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticache" - }, - { - "Action": [ - "dynamodb:DescribeReservedCapacityOfferings", - "dynamodb:DescribeReservedCapacity" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyDynamoDB" - }, - { - "Action": [ - "ec2:DescribeHostReservations", - "ec2:DescribeReservedInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyEC2" - }, - { - "Action": [ - "savingsplans:describe*", - "savingsplans:list*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlySavingsPlans" - }, - { - "Action": [ - "aws-portal:ViewBilling", - "aws-portal:ViewUsage" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingdeprecatedJuly6" - }, - { - "Action": [ - "cur:DescribeReportDefinitions", - "ce:Get*", - "ce:Describe*", - "ce:List*", - "account:GetAccountInformation", - "billing:Get*", - "consolidatedbilling:Get*", - "consolidatedbilling:List*", - "invoicing:List*", - "invoicing:Get*", - "cur:Get*", - "cur:Validate*", - "freetier:Get*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingPolicy" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads", - "s3:AbortMultipartUpload", - "s3:ListMultipartUploadParts", - "s3:PutObject", - "s3:ListBucket", - "s3:List*", - "s3:PutObjectTagging", - "s3:PutObjectAcl" - ], - "Resource": "arn:aws:s3:::sc-customer-*", - "Effect": "Allow", - "Sid": "S3SyncPermissions" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions", - "s3:ListBucketMultipartUploads", - "s3:GetBucketLocation" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "CostAndUsageBucket" - } - ] - ] - } - ], - "Effect": "Allow", - "Sid": "S3CURBucket" - }, - { - "Action": [ - "s3:get*", - "s3:List*", - "s3:Describe*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "CostAndUsageBucket" - }, - "/*" - ] - ] - } - ], - "Effect": "Allow", - "Sid": "S3CURObject" - } - ] - } - } - }, - "SpotFinOpsRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": { - "AWS": [ - "arn:aws:iam::884866656237:role/eco-base-cdk-stack-globalclientassumerole", - "arn:aws:iam::884866656237:role/Admin", - "arn:aws:iam::884866656237:role/DevRole", - "arn:aws:iam::884866656237:role/ReadOnly", - "arn:aws:iam::627743545735:role/Admin", - "arn:aws:iam::627743545735:role/eco-base-cdk-stack-globalclientassumerole" - ] - }, - "Effect": "Allow" - } - ] - }, - "Description": "Spot by NetApp ReadOnly Finops IAM Role", - "ManagedPolicyArns": [ - { - "Ref": "SpotFinOpsManagedPolicy" - }, - "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" - ], - "RoleName": { - "Ref": "RoleName" - } - } - } - } -} \ No newline at end of file diff --git a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.json b/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.json deleted file mode 100644 index 35b8e3e..0000000 --- a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.json +++ /dev/null @@ -1,242 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Outputs": { - "SpotFinOpsRoleArn": { - "Value": { - "Fn::GetAtt": [ - "SpotFinOpsRole", - "Arn" - ] - } - }, - "CostAndUsageBucket": { - "Value": {"Ref": "CostAndUsageBucket"} - }, - "ExternalId": { - "Value": {"Ref": "ExternalId"} - } - }, - "Parameters": { - "CostAndUsageBucket": { - "Type": "String", - "Description": "The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports" - }, - "RoleName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly" - }, - "PolicyName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly_Policy" - }, - "ExternalId": { - "Type": "String" - } - }, - "Resources": { - "SpotFinOpsManagedPolicy": { - "Type": "AWS::IAM::ManagedPolicy", - "Properties": { - "ManagedPolicyName": { "Ref" : "PolicyName" }, - "Description": "Spot by NetApp Finops ReadOnly Policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "es:ListElasticsearchInstanceTypes", - "es:DescribeReservedElasticsearchInstanceOfferings", - "es:DescribeReservedElasticsearchInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticSearch" - }, - { - "Action": [ - "rds:DescribeReservedDBInstances", - "rds:DescribeDBInstances", - "rds:DescribeReservedDBInstancesOfferings" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRDS" - }, - { - "Action": [ - "redshift:DescribeReservedNodeOfferings", - "redshift:DescribeReservedNodes", - "redshift:DescribeClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRedshift" - }, - { - "Action": [ - "elasticache:DescribeReservedCacheNodesOfferings", - "elasticache:DescribeReservedCacheNodes", - "elasticache:DescribeCacheClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticache" - }, - { - "Action": [ - "dynamodb:DescribeReservedCapacityOfferings", - "dynamodb:DescribeReservedCapacity" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyDynamoDB" - }, - { - "Action": [ - "ec2:DescribeHostReservations", - "ec2:DescribeReservedInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyEC2" - }, - { - "Action": [ - "savingsplans:describe*", - "savingsplans:list*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlySavingsPlans" - }, - { - "Action": [ - "aws-portal:ViewBilling", - "aws-portal:ViewUsage" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingdeprecatedJuly6" - }, - { - "Action": [ - "cur:DescribeReportDefinitions", - "ce:Get*", - "ce:Describe*", - "ce:List*", - "account:GetAccountInformation", - "billing:Get*", - "consolidatedbilling:Get*", - "consolidatedbilling:List*", - "invoicing:List*", - "invoicing:Get*", - "cur:Get*", - "cur:Validate*", - "freetier:Get*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingPolicy" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads", - "s3:AbortMultipartUpload", - "s3:ListMultipartUploadParts", - "s3:PutObject", - "s3:ListBucket", - "s3:List*", - "s3:PutObjectTagging", - "s3:PutObjectAcl" - ], - "Resource": "arn:aws:s3:::sc-customer-*", - "Effect": "Allow", - "Sid": "S3SyncPermissions" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions", - "s3:ListBucketMultipartUploads", - "s3:GetBucketLocation" - ], - "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" }]]} - ], - "Effect": "Allow", - "Sid": "S3CURBucket" - }, - { - "Action": [ - "s3:get*", - "s3:List*", - "s3:Describe*" - ], - "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" },"/*"]]} - ], - "Effect": "Allow", - "Sid": "S3CURObject" - } - ] - } - } - }, - "SpotFinOpsRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": { - "AWS": ["arn:aws:iam::884866656237:role/eco-base-cdk-stack-globalclientassumerole", - "arn:aws:iam::884866656237:role/Admin", - "arn:aws:iam::884866656237:role/DevRole", - "arn:aws:iam::884866656237:role/ReadOnly", - "arn:aws:iam::627743545735:role/Admin", - "arn:aws:iam::627743545735:role/eco-base-cdk-stack-globalclientassumerole"] - }, - "Effect": "Allow", - "Condition": { - "StringEquals": { - "sts:ExternalId": { - "Ref": "ExternalId" - } - } - } - } - ] - }, - "Description" : "Spot by NetApp ReadOnly Finops IAM Role", - "ManagedPolicyArns": [ - { - "Ref": "SpotFinOpsManagedPolicy" - }, - "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" - ], - "RoleName" : { "Ref" : "RoleName" } - } - } - } -} \ No newline at end of file diff --git a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.json b/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.json deleted file mode 100644 index c840201..0000000 --- a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.json +++ /dev/null @@ -1,226 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Outputs": { - "SpotFinOpsRoleArn": { - "Value": { - "Fn::GetAtt": [ - "SpotFinOpsRole", - "Arn" - ] - } - } - }, - "Parameters": { - "CostAndUsageBucket": { - "Type": "String", - "Description": "The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports" - }, - "RoleName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly" - }, - "PolicyName": { - "Type": "String", - "Default": "SpotByNetApp_Finops_ReadOnly_Policy" - } - }, - "Resources": { - "SpotFinOpsManagedPolicy": { - "Type": "AWS::IAM::ManagedPolicy", - "Properties": { - "ManagedPolicyName": { "Ref" : "PolicyName" }, - "Description": "Spot by NetApp Finops ReadOnly Policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "es:ListElasticsearchInstanceTypes", - "es:DescribeReservedElasticsearchInstanceOfferings", - "es:DescribeReservedElasticsearchInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticSearch" - }, - { - "Action": [ - "rds:DescribeReservedDBInstances", - "rds:DescribeDBInstances", - "rds:DescribeReservedDBInstancesOfferings" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRDS" - }, - { - "Action": [ - "redshift:DescribeReservedNodeOfferings", - "redshift:DescribeReservedNodes", - "redshift:DescribeClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyRedshift" - }, - { - "Action": [ - "elasticache:DescribeReservedCacheNodesOfferings", - "elasticache:DescribeReservedCacheNodes", - "elasticache:DescribeCacheClusters" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyElasticache" - }, - { - "Action": [ - "dynamodb:DescribeReservedCapacityOfferings", - "dynamodb:DescribeReservedCapacity" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyDynamoDB" - }, - { - "Action": [ - "ec2:DescribeHostReservations", - "ec2:DescribeReservedInstances" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlyEC2" - }, - { - "Action": [ - "savingsplans:describe*", - "savingsplans:list*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "ReadOnlySavingsPlans" - }, - { - "Action": [ - "aws-portal:ViewBilling", - "aws-portal:ViewUsage" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingdeprecatedJuly6" - }, - { - "Action": [ - "cur:DescribeReportDefinitions", - "ce:Get*", - "ce:Describe*", - "ce:List*", - "account:GetAccountInformation", - "billing:Get*", - "consolidatedbilling:Get*", - "consolidatedbilling:List*", - "invoicing:List*", - "invoicing:Get*", - "cur:Get*", - "cur:Validate*", - "freetier:Get*" - ], - "Resource": [ - "*" - ], - "Effect": "Allow", - "Sid": "BillingPolicy" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads", - "s3:AbortMultipartUpload", - "s3:ListMultipartUploadParts", - "s3:PutObject", - "s3:ListBucket", - "s3:List*", - "s3:PutObjectTagging", - "s3:PutObjectAcl" - ], - "Resource": "arn:aws:s3:::sc-customer-*", - "Effect": "Allow", - "Sid": "S3SyncPermissions" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions", - "s3:ListBucketMultipartUploads", - "s3:GetBucketLocation" - ], - "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" }]]} - ], - "Effect": "Allow", - "Sid": "S3CURBucket" - }, - { - "Action": [ - "s3:get*", - "s3:List*", - "s3:Describe*" - ], - "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" },"/*"]]} - ], - "Effect": "Allow", - "Sid": "S3CURObject" - } - ] - } - } - }, - "SpotFinOpsRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": { - "AWS": ["arn:aws:iam::884866656237:role/eco-base-cdk-stack-globalclientassumerole", - "arn:aws:iam::884866656237:role/Admin", - "arn:aws:iam::884866656237:role/DevRole", - "arn:aws:iam::884866656237:role/ReadOnly", - "arn:aws:iam::627743545735:role/Admin", - "arn:aws:iam::627743545735:role/eco-base-cdk-stack-globalclientassumerole"] - }, - "Effect": "Allow" - } - ] - }, - "Description" : "Spot by NetApp ReadOnly Finops IAM Role", - "ManagedPolicyArns": [ - { - "Ref": "SpotFinOpsManagedPolicy" - }, - "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" - ], - "RoleName" : { "Ref" : "RoleName" } - } - } - } -} \ No newline at end of file diff --git a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only.json b/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only.json index d79ba2f..5f6a16f 100644 --- a/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only.json +++ b/CloudFormation/IAM-Roles/Eco/json/spot-iam-finopsrole-stack-restricted-read-only.json @@ -22,17 +22,37 @@ "PolicyName": { "Type": "String", "Default": "SpotByNetApp_Finops_ReadOnly_Policy" + }, + "ExternalId": { + "Type": "String" } }, "Resources": { "SpotFinOpsManagedPolicy": { "Type": "AWS::IAM::ManagedPolicy", "Properties": { - "ManagedPolicyName": { "Ref" : "PolicyName" }, + "ManagedPolicyName": { + "Ref": "PolicyName" + }, "Description": "Spot by NetApp Finops ReadOnly Policy", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ + { + "Action": [ + "cloudformation:Describe*", + "cloudformation:EstimateTemplateCost", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:ValidateTemplate", + "cloudformation:Detect*" + ], + "Resource": [ + "*" + ], + "Effect": "Allow", + "Sid": "ReadOnlyCloudFormation" + }, { "Action": [ "es:ListElasticsearchInstanceTypes", @@ -49,7 +69,8 @@ "Action": [ "rds:DescribeReservedDBInstances", "rds:DescribeDBInstances", - "rds:DescribeReservedDBInstancesOfferings" + "rds:DescribeReservedDBInstancesOfferings", + "rds:ListTagsForResource" ], "Resource": [ "*" @@ -73,7 +94,9 @@ "Action": [ "elasticache:DescribeReservedCacheNodesOfferings", "elasticache:DescribeReservedCacheNodes", - "elasticache:DescribeCacheClusters" + "elasticache:DescribeCacheClusters", + "elasticache:ListAllowedNodeTypeModifications", + "elasticache:ListTagsForResource" ], "Resource": [ "*" @@ -105,8 +128,11 @@ }, { "Action": [ - "savingsplans:describe*", - "savingsplans:list*" + "savingsplans:DescribeSavingsPlanRates", + "savingsplans:DescribeSavingsPlans", + "savingsplans:DescribeSavingsPlansOfferingRates", + "savingsplans:DescribeSavingsPlansOfferings", + "savingsplans:ListTagsForResource" ], "Resource": [ "*" @@ -116,47 +142,90 @@ }, { "Action": [ - "aws-portal:ViewBilling", - "aws-portal:ViewUsage" + "account:GetAccountInformation", + "billing:GetBillingData", + "billing:GetBillingDetails", + "billing:GetBillingNotifications", + "billing:GetBillingPreferences", + "billing:GetContractInformation", + "billing:GetCredits", + "billing:GetIAMAccessPreference", + "billing:GetSellerOfRecord", + "billing:ListBillingViews", + "ce:DescribeNotificationSubscription", + "ce:DescribeReport", + "ce:GetAnomalies", + "ce:GetAnomalyMonitors", + "ce:GetAnomalySubscriptions", + "ce:GetCostAndUsage", + "ce:GetCostAndUsageWithResources", + "ce:GetCostCategories", + "ce:GetCostForecast", + "ce:GetDimensionValues", + "ce:GetPreferences", + "ce:GetReservationCoverage", + "ce:GetReservationPurchaseRecommendation", + "ce:GetReservationUtilization", + "ce:GetRightsizingRecommendation", + "ce:GetSavingsPlansCoverage", + "ce:GetSavingsPlansPurchaseRecommendation", + "ce:GetSavingsPlansUtilization", + "ce:GetSavingsPlansUtilizationDetails", + "ce:GetTags", + "ce:GetUsageForecast", + "ce:ListCostAllocationTags", + "ce:ListSavingsPlansPurchaseRecommendationGeneration", + "consolidatedbilling:GetAccountBillingRole", + "consolidatedbilling:ListLinkedAccounts", + "cur:GetClassicReport", + "cur:GetClassicReportPreferences", + "cur:ValidateReportDestination", + "freetier:GetFreeTierAlertPreference", + "freetier:GetFreeTierUsage", + "invoicing:GetInvoiceEmailDeliveryPreferences", + "invoicing:GetInvoicePDF", + "invoicing:ListInvoiceSummaries", + "payments:GetPaymentInstrument", + "payments:GetPaymentStatus", + "payments:ListPaymentPreferences", + "tax:GetTaxInheritance", + "tax:GetTaxRegistrationDocument", + "tax:ListTaxRegistrations" ], "Resource": [ "*" ], "Effect": "Allow", - "Sid": "BillingdeprecatedJuly6" + "Sid": "ReadOnlyViewBilling" }, { "Action": [ - "cur:DescribeReportDefinitions", - "ce:Get*", - "ce:Describe*", - "ce:List*", - "account:GetAccountInformation", - "billing:Get*", - "consolidatedbilling:Get*", - "consolidatedbilling:List*", - "invoicing:List*", - "invoicing:Get*", - "tax:ListTaxRegistrations", - "cur:Get*", - "cur:Validate*", - "freetier:Get*" + "cur:GetUsageReport", + "cur:DescribeReportDefinitions" ], "Resource": [ "*" ], "Effect": "Allow", - "Sid": "BillingPolicy" + "Sid": "ReadOnlyViewUsage" }, { "Action": [ "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads", "s3:AbortMultipartUpload", + "s3:ListAccessPoints", + "s3:ListAccessPointsForObjectLambda", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListBucketVersions", + "s3:ListJobs", + "s3:ListMultiRegionAccessPoints", "s3:ListMultipartUploadParts", + "s3:ListStorageLensConfigurations", + "s3:ListStorageLensConfigurations", + "s3:ListTagsForResource", "s3:PutObject", - "s3:ListBucket", - "s3:List*", "s3:PutObjectTagging", "s3:PutObjectAcl" ], @@ -172,19 +241,40 @@ "s3:GetBucketLocation" ], "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" }]]} + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CostAndUsageBucket" + } + ] + ] + } ], "Effect": "Allow", "Sid": "S3CURBucket" }, { "Action": [ - "s3:get*", + "s3:Get*", "s3:List*", "s3:Describe*" ], "Resource": [ - { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "CostAndUsageBucket" },"/*"]]} + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CostAndUsageBucket" + }, + "/*" + ] + ] + } ], "Effect": "Allow", "Sid": "S3CURObject" @@ -202,22 +292,32 @@ { "Action": "sts:AssumeRole", "Principal": { - "AWS": ["arn:aws:iam::884866656237:root", - "arn:aws:iam::627743545735:root"] + "AWS": [ + "arn:aws:iam::884866656237:root", + "arn:aws:iam::627743545735:root" + ] }, - "Effect": "Allow" + "Effect": "Allow", + "Condition": { + "StringEquals": { + "sts:ExternalId": { + "Ref": "ExternalId" + } + } + } } ] }, - "Description" : "Spot by NetApp ReadOnly Finops IAM Role", + "Description": "Spot by NetApp ReadOnly Finops IAM Role", "ManagedPolicyArns": [ { "Ref": "SpotFinOpsManagedPolicy" - }, - "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess" + } ], - "RoleName" : { "Ref" : "RoleName" } + "RoleName": { + "Ref": "RoleName" + } } } } -} +} \ No newline at end of file diff --git a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.yaml b/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.yaml deleted file mode 100644 index 9065038..0000000 --- a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume-with-externalid.yaml +++ /dev/null @@ -1,168 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Outputs: - SpotFinOpsRoleArn: - Value: !GetAtt 'SpotFinOpsRole.Arn' - CostAndUsageBucket: - Value: !Ref 'CostAndUsageBucket' - ExternalId: - Value: !Ref 'ExternalId' -Parameters: - CostAndUsageBucket: - Type: String - Description: The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports - RoleName: - Type: String - Default: SpotByNetApp_Finops_ReadOnly - PolicyName: - Type: String - Default: SpotByNetApp_Finops_ReadOnly_Policy - ExternalId: - Type: String -Resources: - SpotFinOpsManagedPolicy: - Type: AWS::IAM::ManagedPolicy - Properties: - ManagedPolicyName: !Ref 'PolicyName' - Description: Spot by NetApp Finops ReadOnly Policy - PolicyDocument: - Version: '2012-10-17' - Statement: - - Action: - - es:ListElasticsearchInstanceTypes - - es:DescribeReservedElasticsearchInstanceOfferings - - es:DescribeReservedElasticsearchInstances - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyElasticSearch - - Action: - - rds:DescribeReservedDBInstances - - rds:DescribeDBInstances - - rds:DescribeReservedDBInstancesOfferings - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyRDS - - Action: - - redshift:DescribeReservedNodeOfferings - - redshift:DescribeReservedNodes - - redshift:DescribeClusters - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyRedshift - - Action: - - elasticache:DescribeReservedCacheNodesOfferings - - elasticache:DescribeReservedCacheNodes - - elasticache:DescribeCacheClusters - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyElasticache - - Action: - - dynamodb:DescribeReservedCapacityOfferings - - dynamodb:DescribeReservedCapacity - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyDynamoDB - - Action: - - ec2:DescribeHostReservations - - ec2:DescribeReservedInstances - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyEC2 - - Action: - - savingsplans:describe* - - savingsplans:list* - Resource: - - '*' - Effect: Allow - Sid: ReadOnlySavingsPlans - - Action: - - aws-portal:ViewBilling - - aws-portal:ViewUsage - Resource: - - '*' - Effect: Allow - Sid: BillingdeprecatedJuly6 - - Action: - - cur:DescribeReportDefinitions - - ce:Get* - - ce:Describe* - - ce:List* - - account:GetAccountInformation - - billing:Get* - - consolidatedbilling:Get* - - consolidatedbilling:List* - - invoicing:List* - - invoicing:Get* - - cur:Get* - - cur:Validate* - - freetier:Get* - Resource: - - '*' - Effect: Allow - Sid: BillingPolicy - - Action: - - s3:GetBucketLocation - - s3:ListBucketMultipartUploads - - s3:AbortMultipartUpload - - s3:ListMultipartUploadParts - - s3:PutObject - - s3:ListBucket - - s3:List* - - s3:PutObjectTagging - - s3:PutObjectAcl - Resource: arn:aws:s3:::sc-customer-* - Effect: Allow - Sid: S3SyncPermissions - - Action: - - s3:ListBucket - - s3:ListBucketVersions - - s3:ListBucketMultipartUploads - - s3:GetBucketLocation - Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' - Effect: Allow - Sid: S3CURBucket - - Action: - - s3:get* - - s3:List* - - s3:Describe* - Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' - - /* - Effect: Allow - Sid: S3CURObject - SpotFinOpsRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Action: sts:AssumeRole - Principal: - AWS: - - arn:aws:iam::884866656237:role/eco-base-cdk-stack-globalclientassumerole - - arn:aws:iam::884866656237:role/Admin - - arn:aws:iam::884866656237:role/DevRole - - arn:aws:iam::884866656237:role/ReadOnly - - arn:aws:iam::627743545735:role/Admin - - arn:aws:iam::627743545735:role/eco-base-cdk-stack-globalclientassumerole - Effect: Allow - Condition: - StringEquals: - sts:ExternalId: !Ref 'ExternalId' - Description: Spot by NetApp ReadOnly Finops IAM Role - ManagedPolicyArns: - - !Ref 'SpotFinOpsManagedPolicy' - - arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess - RoleName: !Ref 'RoleName' diff --git a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.yaml b/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.yaml deleted file mode 100644 index 2411249..0000000 --- a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only-limited-assume.yaml +++ /dev/null @@ -1,159 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Outputs: - SpotFinOpsRoleArn: - Value: !GetAtt 'SpotFinOpsRole.Arn' -Parameters: - CostAndUsageBucket: - Type: String - Description: The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports - RoleName: - Type: String - Default: SpotByNetApp_Finops_ReadOnly - PolicyName: - Type: String - Default: SpotByNetApp_Finops_ReadOnly_Policy -Resources: - SpotFinOpsManagedPolicy: - Type: AWS::IAM::ManagedPolicy - Properties: - ManagedPolicyName: !Ref 'PolicyName' - Description: Spot by NetApp Finops ReadOnly Policy - PolicyDocument: - Version: '2012-10-17' - Statement: - - Action: - - es:ListElasticsearchInstanceTypes - - es:DescribeReservedElasticsearchInstanceOfferings - - es:DescribeReservedElasticsearchInstances - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyElasticSearch - - Action: - - rds:DescribeReservedDBInstances - - rds:DescribeDBInstances - - rds:DescribeReservedDBInstancesOfferings - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyRDS - - Action: - - redshift:DescribeReservedNodeOfferings - - redshift:DescribeReservedNodes - - redshift:DescribeClusters - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyRedshift - - Action: - - elasticache:DescribeReservedCacheNodesOfferings - - elasticache:DescribeReservedCacheNodes - - elasticache:DescribeCacheClusters - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyElasticache - - Action: - - dynamodb:DescribeReservedCapacityOfferings - - dynamodb:DescribeReservedCapacity - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyDynamoDB - - Action: - - ec2:DescribeHostReservations - - ec2:DescribeReservedInstances - Resource: - - '*' - Effect: Allow - Sid: ReadOnlyEC2 - - Action: - - savingsplans:describe* - - savingsplans:list* - Resource: - - '*' - Effect: Allow - Sid: ReadOnlySavingsPlans - - Action: - - aws-portal:ViewBilling - - aws-portal:ViewUsage - Resource: - - '*' - Effect: Allow - Sid: BillingdeprecatedJuly6 - - Action: - - cur:DescribeReportDefinitions - - ce:Get* - - ce:Describe* - - ce:List* - - account:GetAccountInformation - - billing:Get* - - consolidatedbilling:Get* - - consolidatedbilling:List* - - invoicing:List* - - invoicing:Get* - - cur:Get* - - cur:Validate* - - freetier:Get* - Resource: - - '*' - Effect: Allow - Sid: BillingPolicy - - Action: - - s3:GetBucketLocation - - s3:ListBucketMultipartUploads - - s3:AbortMultipartUpload - - s3:ListMultipartUploadParts - - s3:PutObject - - s3:ListBucket - - s3:List* - - s3:PutObjectTagging - - s3:PutObjectAcl - Resource: arn:aws:s3:::sc-customer-* - Effect: Allow - Sid: S3SyncPermissions - - Action: - - s3:ListBucket - - s3:ListBucketVersions - - s3:ListBucketMultipartUploads - - s3:GetBucketLocation - Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' - Effect: Allow - Sid: S3CURBucket - - Action: - - s3:get* - - s3:List* - - s3:Describe* - Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' - - /* - Effect: Allow - Sid: S3CURObject - SpotFinOpsRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Action: sts:AssumeRole - Principal: - AWS: - - arn:aws:iam::884866656237:role/eco-base-cdk-stack-globalclientassumerole - - arn:aws:iam::884866656237:role/Admin - - arn:aws:iam::884866656237:role/DevRole - - arn:aws:iam::884866656237:role/ReadOnly - - arn:aws:iam::627743545735:role/Admin - - arn:aws:iam::627743545735:role/eco-base-cdk-stack-globalclientassumerole - Effect: Allow - Description: Spot by NetApp ReadOnly Finops IAM Role - ManagedPolicyArns: - - !Ref 'SpotFinOpsManagedPolicy' - - arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess - RoleName: !Ref 'RoleName' diff --git a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only.yaml b/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only.yaml index 9a5c87a..e6ea854 100644 --- a/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only.yaml +++ b/CloudFormation/IAM-Roles/Eco/yaml/spot-iam-finopsrole-stack-restricted-read-only.yaml @@ -1,155 +1,227 @@ AWSTemplateFormatVersion: '2010-09-09' Outputs: SpotFinOpsRoleArn: - Value: !GetAtt 'SpotFinOpsRole.Arn' + Value: + 'Fn::GetAtt': + - SpotFinOpsRole + - Arn Parameters: CostAndUsageBucket: Type: String - Description: The bucket name of where the *HOURLY* Cost and Usage Report is located. https://console.aws.amazon.com/billing/home?#/reports + Description: >- + The bucket name of where the *HOURLY* Cost and Usage Report is located. + https://console.aws.amazon.com/billing/home?#/reports RoleName: Type: String Default: SpotByNetApp_Finops_ReadOnly PolicyName: Type: String Default: SpotByNetApp_Finops_ReadOnly_Policy + ExternalId: + Type: String Resources: SpotFinOpsManagedPolicy: - Type: AWS::IAM::ManagedPolicy + Type: 'AWS::IAM::ManagedPolicy' Properties: - ManagedPolicyName: !Ref 'PolicyName' + ManagedPolicyName: + Ref: PolicyName Description: Spot by NetApp Finops ReadOnly Policy PolicyDocument: Version: '2012-10-17' Statement: - Action: - - es:ListElasticsearchInstanceTypes - - es:DescribeReservedElasticsearchInstanceOfferings - - es:DescribeReservedElasticsearchInstances + - 'cloudformation:Describe*' + - 'cloudformation:EstimateTemplateCost' + - 'cloudformation:Get*' + - 'cloudformation:List*' + - 'cloudformation:ValidateTemplate' + - 'cloudformation:Detect*' + Resource: + - '*' + Effect: Allow + Sid: ReadOnlyCloudFormation + - Action: + - 'es:ListElasticsearchInstanceTypes' + - 'es:DescribeReservedElasticsearchInstanceOfferings' + - 'es:DescribeReservedElasticsearchInstances' Resource: - '*' Effect: Allow Sid: ReadOnlyElasticSearch - Action: - - rds:DescribeReservedDBInstances - - rds:DescribeDBInstances - - rds:DescribeReservedDBInstancesOfferings + - 'rds:DescribeReservedDBInstances' + - 'rds:DescribeDBInstances' + - 'rds:DescribeReservedDBInstancesOfferings' + - 'rds:ListTagsForResource' Resource: - '*' Effect: Allow Sid: ReadOnlyRDS - Action: - - redshift:DescribeReservedNodeOfferings - - redshift:DescribeReservedNodes - - redshift:DescribeClusters + - 'redshift:DescribeReservedNodeOfferings' + - 'redshift:DescribeReservedNodes' + - 'redshift:DescribeClusters' Resource: - '*' Effect: Allow Sid: ReadOnlyRedshift - Action: - - elasticache:DescribeReservedCacheNodesOfferings - - elasticache:DescribeReservedCacheNodes - - elasticache:DescribeCacheClusters + - 'elasticache:DescribeReservedCacheNodesOfferings' + - 'elasticache:DescribeReservedCacheNodes' + - 'elasticache:DescribeCacheClusters' + - 'elasticache:ListAllowedNodeTypeModifications' + - 'elasticache:ListTagsForResource' Resource: - '*' Effect: Allow Sid: ReadOnlyElasticache - Action: - - dynamodb:DescribeReservedCapacityOfferings - - dynamodb:DescribeReservedCapacity + - 'dynamodb:DescribeReservedCapacityOfferings' + - 'dynamodb:DescribeReservedCapacity' Resource: - '*' Effect: Allow Sid: ReadOnlyDynamoDB - Action: - - ec2:DescribeHostReservations - - ec2:DescribeReservedInstances + - 'ec2:DescribeHostReservations' + - 'ec2:DescribeReservedInstances' Resource: - '*' Effect: Allow Sid: ReadOnlyEC2 - Action: - - savingsplans:describe* - - savingsplans:list* + - 'savingsplans:DescribeSavingsPlanRates' + - 'savingsplans:DescribeSavingsPlans' + - 'savingsplans:DescribeSavingsPlansOfferingRates' + - 'savingsplans:DescribeSavingsPlansOfferings' + - 'savingsplans:ListTagsForResource' Resource: - '*' Effect: Allow Sid: ReadOnlySavingsPlans - Action: - - aws-portal:ViewBilling - - aws-portal:ViewUsage + - 'account:GetAccountInformation' + - 'billing:GetBillingData' + - 'billing:GetBillingDetails' + - 'billing:GetBillingNotifications' + - 'billing:GetBillingPreferences' + - 'billing:GetContractInformation' + - 'billing:GetCredits' + - 'billing:GetIAMAccessPreference' + - 'billing:GetSellerOfRecord' + - 'billing:ListBillingViews' + - 'ce:DescribeNotificationSubscription' + - 'ce:DescribeReport' + - 'ce:GetAnomalies' + - 'ce:GetAnomalyMonitors' + - 'ce:GetAnomalySubscriptions' + - 'ce:GetCostAndUsage' + - 'ce:GetCostAndUsageWithResources' + - 'ce:GetCostCategories' + - 'ce:GetCostForecast' + - 'ce:GetDimensionValues' + - 'ce:GetPreferences' + - 'ce:GetReservationCoverage' + - 'ce:GetReservationPurchaseRecommendation' + - 'ce:GetReservationUtilization' + - 'ce:GetRightsizingRecommendation' + - 'ce:GetSavingsPlansCoverage' + - 'ce:GetSavingsPlansPurchaseRecommendation' + - 'ce:GetSavingsPlansUtilization' + - 'ce:GetSavingsPlansUtilizationDetails' + - 'ce:GetTags' + - 'ce:GetUsageForecast' + - 'ce:ListCostAllocationTags' + - 'ce:ListSavingsPlansPurchaseRecommendationGeneration' + - 'consolidatedbilling:GetAccountBillingRole' + - 'consolidatedbilling:ListLinkedAccounts' + - 'cur:GetClassicReport' + - 'cur:GetClassicReportPreferences' + - 'cur:ValidateReportDestination' + - 'freetier:GetFreeTierAlertPreference' + - 'freetier:GetFreeTierUsage' + - 'invoicing:GetInvoiceEmailDeliveryPreferences' + - 'invoicing:GetInvoicePDF' + - 'invoicing:ListInvoiceSummaries' + - 'payments:GetPaymentInstrument' + - 'payments:GetPaymentStatus' + - 'payments:ListPaymentPreferences' + - 'tax:GetTaxInheritance' + - 'tax:GetTaxRegistrationDocument' + - 'tax:ListTaxRegistrations' Resource: - '*' Effect: Allow - Sid: BillingdeprecatedJuly6 + Sid: ReadOnlyViewBilling - Action: - - cur:DescribeReportDefinitions - - ce:Get* - - ce:Describe* - - ce:List* - - account:GetAccountInformation - - billing:Get* - - consolidatedbilling:Get* - - consolidatedbilling:List* - - invoicing:List* - - invoicing:Get* - - cur:Get* - - cur:Validate* - - freetier:Get* + - 'cur:GetUsageReport' + - 'cur:DescribeReportDefinitions' Resource: - '*' Effect: Allow - Sid: BillingPolicy + Sid: ReadOnlyViewUsage - Action: - - s3:GetBucketLocation - - s3:ListBucketMultipartUploads - - s3:AbortMultipartUpload - - s3:ListMultipartUploadParts - - s3:PutObject - - s3:ListBucket - - s3:List* - - s3:PutObjectTagging - - s3:PutObjectAcl - Resource: arn:aws:s3:::sc-customer-* + - 's3:GetBucketLocation' + - 's3:AbortMultipartUpload' + - 's3:ListAccessPoints' + - 's3:ListAccessPointsForObjectLambda' + - 's3:ListAllMyBuckets' + - 's3:ListBucket' + - 's3:ListBucketMultipartUploads' + - 's3:ListBucketVersions' + - 's3:ListJobs' + - 's3:ListMultiRegionAccessPoints' + - 's3:ListMultipartUploadParts' + - 's3:ListStorageLensConfigurations' + - 's3:ListStorageLensConfigurations' + - 's3:ListTagsForResource' + - 's3:PutObject' + - 's3:PutObjectTagging' + - 's3:PutObjectAcl' + Resource: 'arn:aws:s3:::sc-customer-*' Effect: Allow Sid: S3SyncPermissions - Action: - - s3:ListBucket - - s3:ListBucketVersions - - s3:ListBucketMultipartUploads - - s3:GetBucketLocation + - 's3:ListBucket' + - 's3:ListBucketVersions' + - 's3:ListBucketMultipartUploads' + - 's3:GetBucketLocation' Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' + - 'Fn::Join': + - '' + - - 'arn:aws:s3:::' + - Ref: CostAndUsageBucket Effect: Allow Sid: S3CURBucket - Action: - - s3:get* - - s3:List* - - s3:Describe* + - 's3:Get*' + - 's3:List*' + - 's3:Describe*' Resource: - - !Join - - '' - - - 'arn:aws:s3:::' - - !Ref 'CostAndUsageBucket' - - /* + - 'Fn::Join': + - '' + - - 'arn:aws:s3:::' + - Ref: CostAndUsageBucket + - /* Effect: Allow Sid: S3CURObject SpotFinOpsRole: - Type: AWS::IAM::Role + Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - - Action: sts:AssumeRole + - Action: 'sts:AssumeRole' Principal: AWS: - - arn:aws:iam::884866656237:root - - arn:aws:iam::627743545735:root + - 'arn:aws:iam::884866656237:root' + - 'arn:aws:iam::627743545735:root' Effect: Allow + Condition: + StringEquals: + sts:ExternalId: !Ref 'ExternalId' Description: Spot by NetApp ReadOnly Finops IAM Role ManagedPolicyArns: - - !Ref 'SpotFinOpsManagedPolicy' - - arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess - RoleName: !Ref 'RoleName' + - Ref: SpotFinOpsManagedPolicy + RoleName: + Ref: RoleName diff --git a/Policies/AWS/Spot-AWS-Restricted-EKS.json b/Policies/AWS/Spot-AWS-Restricted-EKS.json index 287607e..f2f6271 100644 --- a/Policies/AWS/Spot-AWS-Restricted-EKS.json +++ b/Policies/AWS/Spot-AWS-Restricted-EKS.json @@ -117,7 +117,9 @@ { "Sid": "AccessEks", "Action": [ - "eks:ListClusters" + "eks:ListClusters", + "eks:DescribeNodegroup", + "eks:ListNodegroups" ], "Effect": "Allow", "Resource": [ diff --git a/Policies/AWS/Spot-AWS-Restricted-Full-Permissions.json b/Policies/AWS/Spot-AWS-Restricted-Full-Permissions.json index 03094a1..4140614 100644 --- a/Policies/AWS/Spot-AWS-Restricted-Full-Permissions.json +++ b/Policies/AWS/Spot-AWS-Restricted-Full-Permissions.json @@ -112,6 +112,7 @@ "iam:GetPolicyVersion", "iam:ListPolicies", "iam:GetPolicy", + "iam:PassRole", "iam:ListAttachedRolePolicies", "organizations:ListAccounts", "iam:CreateServiceLinkedRole" diff --git a/Policies/Azure/Spot-Azure-ECO-Full-Permission.json b/Policies/Azure/Spot-Azure-ECO-Full-Permission.json deleted file mode 100644 index babcdc1..0000000 --- a/Policies/Azure/Spot-Azure-ECO-Full-Permission.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "properties": { - "roleName": "Spot Eco", - "description": "Spot Eco full permission role to be granted to Spot Eco users to each subscription to enable automation", - "assignableScopes": ["/subscriptions/"], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/roleAssignments/read", - "Microsoft.Advisor/generateRecommendations/action", - "Microsoft.Advisor/register/action", - "Microsoft.Advisor/unregister/action", - "Microsoft.Advisor/configurations/read", - "Microsoft.Advisor/configurations/write", - "Microsoft.Advisor/generateRecommendations/read", - "Microsoft.Advisor/operations/read", - "Microsoft.Advisor/recommendations/read", - "Microsoft.Advisor/recommendations/available/action", - "Microsoft.Advisor/recommendations/suppressions/read", - "Microsoft.Advisor/recommendations/suppressions/write", - "Microsoft.Advisor/recommendations/suppressions/delete", - "Microsoft.Capacity/catalogs/read", - "Microsoft.Capacity/register/action", - "Microsoft.Compute/register/action", - "Microsoft.Compute/capacityReservationGroups/read", - "Microsoft.Compute/operations/read", - "Microsoft.Compute/availabilitySets/vmSizes/read", - "Microsoft.Compute/availabilitySets/read", - "Microsoft.Compute/capacityReservationGroups/capacityReservations/read", - "Microsoft.Compute/locations/capsOperations/read", - "Microsoft.Compute/cloudServices/instanceView/read", - "Microsoft.Compute/cloudServices/providers/Microsoft.Insights/metricDefinitions/read", - "Microsoft.Compute/cloudServices/roles/providers/Microsoft.Insights/metricDefinitions/read", - "Microsoft.Compute/locations/publishers/artifacttypes/offers/skus/read", - "Microsoft.Compute/skus/read", - "Microsoft.Compute/locations/usages/read", - "Microsoft.Compute/virtualMachineScaleSets/vmSizes/read", - "Microsoft.Compute/virtualMachineScaleSets/virtualMachines/read", - "Microsoft.Compute/locations/vmSizes/read", - "Microsoft.Compute/virtualMachines/read", - "Microsoft.Compute/virtualMachines/vmSizes/read", - "Microsoft.Consumption/register/action", - "Microsoft.Consumption/reservationRecommendations/read", - "Microsoft.CostManagement/query/action", - "Microsoft.CostManagement/reports/action", - "Microsoft.CostManagement/exports/action", - "Microsoft.CostManagement/register/action", - "Microsoft.CostManagement/views/action", - "Microsoft.CostManagement/forecast/action", - "Microsoft.CostManagement/alerts/read", - "Microsoft.CostManagement/cloudConnectors/read", - "Microsoft.CostManagement/dimensions/read", - "Microsoft.CostManagement/exports/read", - "Microsoft.CostManagement/exports/write", - "Microsoft.CostManagement/exports/delete", - "Microsoft.CostManagement/exports/run/action", - "Microsoft.CostManagement/externalBillingAccounts/read", - "Microsoft.CostManagement/externalBillingAccounts/query/action", - "Microsoft.CostManagement/externalBillingAccounts/forecast/action", - "Microsoft.CostManagement/externalBillingAccounts/dimensions/read", - "Microsoft.CostManagement/externalBillingAccounts/query/read", - "Microsoft.CostManagement/externalBillingAccounts/externalSubscriptions/read", - "Microsoft.CostManagement/externalBillingAccounts/forecast/read", - "Microsoft.CostManagement/externalSubscriptions/read", - "Microsoft.CostManagement/externalSubscriptions/query/action", - "Microsoft.CostManagement/externalSubscriptions/forecast/action", - "Microsoft.CostManagement/externalSubscriptions/dimensions/read", - "Microsoft.CostManagement/externalSubscriptions/query/read", - "Microsoft.CostManagement/externalSubscriptions/forecast/read", - "Microsoft.CostManagement/forecast/read", - "Microsoft.CostManagement/operations/read", - "Microsoft.CostManagement/query/read", - "Microsoft.CostManagement/reports/read", - "Microsoft.CostManagement/views/read", - "Microsoft.CostManagement/views/delete", - "Microsoft.CostManagement/views/write", - "Microsoft.CostManagement/tenants/register/action", - "Microsoft.CostManagement/budgets/read", - "Microsoft.Insights/MetricDefinitions/Read", - "Microsoft.Insights/Metrics/Read", - "Microsoft.Resources/tags/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Support/supportTickets/read", - "Microsoft.Support/supportTickets/write", - "Microsoft.SQL/register/action" - ], - "notActions": [], - "dataActions": [], - "notDataActions": [] - } - ] - } -} \ No newline at end of file diff --git a/Policies/Azure/Spot-Azure-ECO-ReadOnly.json b/Policies/Azure/Spot-Azure-ECO-ReadOnly.json deleted file mode 100644 index 215ff70..0000000 --- a/Policies/Azure/Spot-Azure-ECO-ReadOnly.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "properties": { - "roleName": "Spot Eco", - "description": "Spot Eco read-only role to be granted to Spot Eco users to each subscription for which an assessment is desired", - "assignableScopes": ["/subscriptions/"], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/roleAssignments/read", - "Microsoft.Advisor/advisorScore/read", - - "Microsoft.Capacity/catalogs/read", - "Microsoft.Capacity/register/action", - "Microsoft.Compute/register/action", - "Microsoft.Compute/capacityReservationGroups/read", - "Microsoft.Compute/operations/read", - "Microsoft.Compute/availabilitySets/vmSizes/read", - "Microsoft.Compute/availabilitySets/read", - "Microsoft.Compute/capacityReservationGroups/capacityReservations/read", - "Microsoft.Compute/locations/capsOperations/read", - "Microsoft.Compute/cloudServices/instanceView/read", - "Microsoft.Compute/cloudServices/providers/Microsoft.Insights/metricDefinitions/read", - "Microsoft.Compute/cloudServices/roles/providers/Microsoft.Insights/metricDefinitions/read", - "Microsoft.Compute/locations/publishers/artifacttypes/offers/skus/read", - "Microsoft.Compute/skus/read", - "Microsoft.Compute/locations/usages/read", - "Microsoft.Compute/virtualMachineScaleSets/vmSizes/read", - "Microsoft.Compute/virtualMachineScaleSets/virtualMachines/read", - "Microsoft.Compute/locations/vmSizes/read", - "Microsoft.Compute/virtualMachines/read", - "Microsoft.Compute/virtualMachines/vmSizes/read", - "Microsoft.Consumption/register/action", - "Microsoft.Consumption/reservationRecommendations/read", - "Microsoft.CostManagement/query/action", - "Microsoft.CostManagement/reports/action", - "Microsoft.CostManagement/exports/action", - "Microsoft.CostManagement/register/action", - "Microsoft.CostManagement/views/action", - "Microsoft.CostManagement/forecast/action", - "Microsoft.CostManagement/alerts/read", - "Microsoft.CostManagement/cloudConnectors/read", - "Microsoft.CostManagement/dimensions/read", - "Microsoft.CostManagement/exports/read", - "Microsoft.CostManagement/exports/write", - "Microsoft.CostManagement/exports/delete", - "Microsoft.CostManagement/exports/run/action", - "Microsoft.CostManagement/externalBillingAccounts/read", - "Microsoft.CostManagement/externalBillingAccounts/query/action", - "Microsoft.CostManagement/externalBillingAccounts/forecast/action", - "Microsoft.CostManagement/externalBillingAccounts/dimensions/read", - "Microsoft.CostManagement/externalBillingAccounts/query/read", - "Microsoft.CostManagement/externalBillingAccounts/externalSubscriptions/read", - "Microsoft.CostManagement/externalBillingAccounts/forecast/read", - "Microsoft.CostManagement/externalSubscriptions/read", - "Microsoft.CostManagement/externalSubscriptions/query/action", - "Microsoft.CostManagement/externalSubscriptions/forecast/action", - "Microsoft.CostManagement/externalSubscriptions/dimensions/read", - "Microsoft.CostManagement/externalSubscriptions/query/read", - "Microsoft.CostManagement/externalSubscriptions/forecast/read", - "Microsoft.CostManagement/forecast/read", - "Microsoft.CostManagement/operations/read", - "Microsoft.CostManagement/query/read", - "Microsoft.CostManagement/reports/read", - "Microsoft.CostManagement/views/read", - "Microsoft.CostManagement/views/delete", - "Microsoft.CostManagement/views/write", - "Microsoft.CostManagement/tenants/register/action", - "Microsoft.CostManagement/budgets/read", - "Microsoft.Insights/MetricDefinitions/Read", - "Microsoft.Insights/Metrics/Read", - "Microsoft.Resources/tags/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.SQL/register/action" - ], - "notActions": [], - "dataActions": [], - "notDataActions": [] - } - ] - } -} \ No newline at end of file diff --git a/Utilities/AzureOnboardingCLI/azure-automatic-role-assignment.py b/Utilities/AzureOnboardingCLI/azure-automatic-role-assignment.py index e06867b..d578603 100644 --- a/Utilities/AzureOnboardingCLI/azure-automatic-role-assignment.py +++ b/Utilities/AzureOnboardingCLI/azure-automatic-role-assignment.py @@ -1,23 +1,71 @@ #!/usr/bin/env import argparse -import json import re import subprocess import time +import uuid +from datetime import datetime import requests -from azure.identity import DefaultAzureCredential -from azure.mgmt.subscription import SubscriptionClient +from requests import ConnectTimeout from spotinst_sdk2 import SpotinstSession +from spotinst_sdk2.client import Client, SpotinstClientException from spotinst_sdk2.models.setup.azure import * CUSTOM_ROLE_NAME = "{customRoleName}" SUBSCRIPTION_ID = "{subscriptionId}" -CUSTOM_ROLE_PERMISSION_URL = ( - "https://nirtest2.s3-us-west-2.amazonaws.com/custom_role_file.json" +CORE_PRODUCT_CUSTOM_ROLE_URL = ( + "https://spotinst-public.s3.amazonaws.com/assets/azure/custom_role_file.json" ) HTTP_OK_RESPONSE_STATUSES = range(200, 300) +SPOT_API_BASE_URL = 'https://api.spotinst.io' +SPOT_SETUP_CI_PATH = "/cbi/v1/setup/account" + + +class Products: + CORE = 'core' + COST_INTELLIGENCE = 'cost-intelligence' + SPOT_SECURITY = 'spot-security' + + +class BuiltInAzureRoles: + READER = 'READER' + STORAGE_BLOB_DATA_READER = 'Storage Blob Data Reader' + + +class LogLevel: + ERROR = "ERROR" + INFO = "INFO" + + +# Pass through argument to subprocess.run(). See documentation there for additional information. When running in Windows +# and seeing errors executing commands, it may be helpful to use `shell=True`. +run_in_shell = False + + +def log(message, log_level=LogLevel.INFO): + print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} [{log_level}] {message}") + + +def get_all_subscriptions_in_tenant(): + """ + Retrieves all the subscriptions in the tenant. + Returns: + list(str): The subscription ids in the tenant. + """ + subscription_ids = run_command("az account subscription list --query [].subscriptionId") + return subscription_ids + + +def get_active_tenant(): + """ + Gets the active tenant. + Returns: + str: The tenant id. + """ + tenant_id = run_command("az account show --query tenantId") + return tenant_id def get_subscription_name(subscription_id): @@ -30,27 +78,54 @@ def get_subscription_name(subscription_id): Returns: str: The display name of the subscription. """ - credential = DefaultAzureCredential() - subscription_client = SubscriptionClient(credential) - subscription = subscription_client.subscriptions.get(subscription_id) - return subscription.display_name + subscription_display_name = run_command( + f'az account subscription show --subscription-id {subscription_id} --query displayName') + + return subscription_display_name -def create_spot_account(name, token): +def get_or_create_spot_account(subscription_id, name, token): """ Creates a spot account with the given name and token. Args: + subscription_id (str) : External provider id. name (str): The name of the spot account. token (str): The authentication token. Returns: str: The ID of the created account. """ - session = SpotinstSession(auth_token=token) - client = session.client("admin") - account_result = client.create_account(name) - return account_result["id"] + try: + session = SpotinstSession(auth_token=token, base_url=SPOT_API_BASE_URL) + except SpotinstClientException as e: + log(f"Spotinst token is invalid.", LogLevel.ERROR) + raise e + + client = session.client("admin", timeout=15) + + try: + existing_accounts = [account["account_id"] for account in client.get_accounts() if + account["provider_external_id"] == subscription_id and account["cloud_provider"] == 'AZURE'] + except ConnectTimeout as e: + log(f"Could not reach Spot API. Please try again later.", LogLevel.ERROR) + raise e + + except Exception as e: + log(f"An exception of type {type(e).__name__} occurred while attempting to retrieve Spot Account information. {str(e)}", LogLevel.ERROR) + raise e + + account_id = existing_accounts.pop() if len(existing_accounts) > 0 else None + + if not account_id: + # there is no existing spot account so create it + account_result = client.create_account(name) + account_id = account_result["id"] + log(f"No Spot Account was not found for the subscription. Created Spot Account {account_id} for subscription {subscription_id}") + else: + log(f"Existing Spot Account found {account_id} for subscription {subscription_id}") + + return account_id def run_command(cmd, *args): @@ -68,25 +143,17 @@ def run_command(cmd, *args): Exception: If the command fails to run, an exception is raised with the command and error message. Example: - >>> run_command("ls", "-l") + #>>> run_command("ls", "-l") {'file1.txt': 'rw-r--r--', 'file2.txt': 'rw-rw-r--'} """ - print(f"Run command: {cmd}") + log(f"Running command: {cmd}") cmd_args = cmd.split() cmd_to_run = cmd_args + list(args) run_process_result = subprocess.run( - cmd_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" - ) # Maybe need to change to check output because in python5 there isn't encoding - - # try: - # output = subprocess.check_output( - # cmnd, stderr=subprocess.STDOUT) - # except subprocess.CalledProcessError as exc: - # print("Status : FAIL", exc.returncode, exc.output) - # else: - # print(f"Output: \n{output}\n") + cmd_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", shell=run_in_shell + ) # Maybe need to change to check output because in python there isn't encoding if run_process_result.returncode != 0: raise Exception( @@ -97,29 +164,32 @@ def run_command(cmd, *args): ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") cleaned_command_result = ansi_escape.sub("", run_process_result.stdout) - result = json.loads(cleaned_command_result) + print(cleaned_command_result) + result = None if (cleaned_command_result is None) or (cleaned_command_result == '') else json.loads(cleaned_command_result) - print("Succeeded to run command: {cmd}") + log(f"Succeeded running command: {cmd}") return result -def display_result(subscription, create_or_get_service_principal_response): +def display_app_result(subscription_id, tenant_id, app_registration_id, client_secret): """ - Display the result of the subscription and create/get service principal response. + Display the result of the subscription and service principal. Args: - subscription (str): The subscription ID. - create_or_get_service_principal_response (dict): The response from the create or get service principal API. + subscription_id (str): The subscription id. + tenant_id (str): The tenant id. + app_registration_id (str): The application registration id associated with the service principal. + client_secret (str): The client secret associated with the service principal. Returns: None """ - print("Your credentials details:") - print(f"Application (client) ID: {create_or_get_service_principal_response['appId']}") - print(f"Client Secret: {create_or_get_service_principal_response['password']}") - print(f"Tenant ID: {create_or_get_service_principal_response['tenant']}") - print(f"Subscription ID: {subscription}") + log("Your credentials details:") + log(f"Application Registration ID: {app_registration_id}") + log(f"Client Secret: {client_secret}") + log(f"Tenant ID: {tenant_id}") + log(f"Subscription ID: {subscription_id}") def set_azure_credentials( @@ -139,16 +209,25 @@ def set_azure_credentials( Returns: bool: True if credentials were set successfully, False otherwise """ - session = SpotinstSession(auth_token=token) - client = session.client("setup_azure") - client.account_id = account_id - azure_credentials = AzureCredentials( - client_id=client_id, - client_secret=client_secret, - tenant_id=tenant_id, - subscription_id=subscription_id, - ) - return client.set_credentials(azure_credentials) + log(f"Updating linked credentials for Spot account {account_id}") + try: + session = SpotinstSession(auth_token=token, base_url=SPOT_API_BASE_URL) + client = session.client("setup_azure", timeout=15) + client.account_id = account_id + azure_credentials = AzureCredentials( + client_id=client_id, + client_secret=client_secret, + tenant_id=tenant_id, + subscription_id=subscription_id, + ) + result = client.set_credentials(azure_credentials) + except SpotinstClientException as e: + # the new credentials may have failed to be validated because of Azure propagation latency. try again after a brief delay + time.sleep(15) + result = client.set_credentials(azure_credentials) + + log(f"Successfully updated linked credentials.") + return result # todo nir - from where take the file in case of using --path param @@ -177,7 +256,7 @@ def build_custom_role(custom_role_name, custom_role_json_local_path, subscriptio .replace(CUSTOM_ROLE_NAME, custom_role_name) ) else: - get_custom_role_from_s3_response = requests.get(CUSTOM_ROLE_PERMISSION_URL) + get_custom_role_from_s3_response = requests.get(CORE_PRODUCT_CUSTOM_ROLE_URL) if get_custom_role_from_s3_response.status_code in HTTP_OK_RESPONSE_STATUSES: result = ( @@ -189,59 +268,70 @@ def build_custom_role(custom_role_name, custom_role_json_local_path, subscriptio return result -def create_required_parameters_for_spot_registrations( - subscription, - custom_role_name, - custom_role_json_local_path, - service_principal_name, - app_registration_id=None, -): - if does_subscription_exist_for_account(subscription): - custom_role_name = create_custom_role( +def get_roles_for_products(products, custom_role_name, custom_role_json_local_path, subscription): + roles = [] + if Products.CORE in products: + custom_role_name = get_or_create_custom_role( custom_role_name, custom_role_json_local_path, subscription ) - print("Waiting 90 seconds for role to propagate.") - time.sleep(90) - if app_registration_id is not None: - create_or_get_service_principal_response = get_service_principal( - app_registration_id, custom_role_name, subscription, service_principal_name - ) - else: - create_or_get_service_principal_response = create_service_principal( - custom_role_name, service_principal_name, subscription - ) + roles.append(custom_role_name) - result = create_or_get_service_principal_response - else: - raise Exception("Could not find the subscription in account subscriptions") + if Products.COST_INTELLIGENCE in products: + # cost intelligence requires the Azure built-in READER role + roles.append(BuiltInAzureRoles.READER) - return result + if Products.SPOT_SECURITY in products: + roles.append(BuiltInAzureRoles.STORAGE_BLOB_DATA_READER) + + return roles -def get_service_principal(app_registration_id, custom_role_name, subscription, spot_account_id): +def create_client_secret(app_registration_id, credential_name): """ - Generate a service principal for the given app registration ID, assign a custom role to the service principal, create a secret for the app registration, and return the result of creating the secret. + Create a secret for the App Registration Parameters: - app_registration_id (str): The ID of the app registration. - - custom_role_name (str): The name of the custom role to assign to the service principal. - - subscription (str): The ID of the subscription. + - credential_name (str): The display name to use for the credential. Returns: - create_secret_result (dict): The result of creating the secret for the app registration. """ - app_reg_show_cmd = f"az ad sp show --id {app_registration_id}" - run_command(app_reg_show_cmd) + create_secret_cmd = f"az ad app credential reset --id {app_registration_id} --append --display-name {credential_name}" + result = run_command(create_secret_cmd) + + return (result["appId"], result["password"], result["tenant"]) - # Assign the custom role to the service principal - app_reg_role_assignment_cmd = f"az role assignment create --assignee {app_registration_id} --role {custom_role_name} --scope /subscriptions/{subscription}" - run_command(app_reg_role_assignment_cmd) - # Create a secret for the App Registration - create_secret_cmd = f"az ad app credential reset --id {app_registration_id} --append --display-name {spot_account_id}" - create_secret_result = run_command(create_secret_cmd) +def assign_roles(app_registration_id, roles, subscription): + """ + Assign the roles to the service principal. + + Parameters: + - app_registration_id (str): The app registration id for the service principal. + see https://learn.microsoft.com/en-us/cli/azure/role/assignment?view=azure-cli-latest#az-role-assignment-create + - roles (list(str)): The names of the roles to assign to the service principal. + - subscription (str): The ID of the subscription. + """ + # Get the object_id for the service principal. This is used with --assignee-object-id to avoid errors caused by propagation latency in AAD Graph. + # see https://learn.microsoft.com/en-us/cli/azure/role/assignment?view=azure-cli-latest#az-role-assignment-create + get_app_object_id_cmd = f"az ad sp show --id {app_registration_id}" + object_id_result = run_command(get_app_object_id_cmd) + app_object_id = object_id_result.get('id') + + for role in roles: + app_reg_role_assignment_cmd = f"az role assignment create --assignee-object-id {app_object_id} --assignee-principal-type ServicePrincipal" + try: + run_command(app_reg_role_assignment_cmd, + "--role", role, + "--scope", f"/subscriptions/{subscription}") + except: + # the role assignment may have failed because of propagation latency. try again after a brief delay + time.sleep(15) + run_command(app_reg_role_assignment_cmd, + "--role", role, + "--scope", f"/subscriptions/{subscription}") - return create_secret_result def does_subscription_exist_for_account(subscription): """ @@ -253,43 +343,46 @@ def does_subscription_exist_for_account(subscription): Returns: bool: True if the subscription exists for the account, False otherwise. """ - get_account_cmd = "az account list --output json --all" + get_account_cmd = "az account list --all" accounts = list(run_command(get_account_cmd)) account_property_with_subscription = next( (account for account in accounts if account["id"] == subscription), None ) if account_property_with_subscription is not None: - print(f"Found the subscription: {subscription} for account: {account_property_with_subscription}") + log(f"Found the subscription: {subscription} for account: {account_property_with_subscription}") return True else: return False -def create_service_principal(custom_role_name, service_principal_name, subscription): +def create_service_principal(service_principal_name): """ - Creates a service principal with the given custom role name, service principal name, and subscription. + Creates a new app registration and corresponding service principal. Args: - custom_role_name (str): The name of the custom role. service_principal_name (str): The name of the service principal. - subscription (str): The subscription ID. Returns: - str: The result of creating the service principal. + (str1, str2, str3) where: + str1: The ID of the app registration. + str2: The client secret that is the result of creating the service principal + str3: The tenant id where app registration and service principal reside """ - print("Create service principal") - create_service_principal_cmd = f"az ad sp create-for-rbac --name {service_principal_name} --role {custom_role_name} --scopes /subscriptions/{subscription}" - result = run_command(create_service_principal_cmd) + log("Creating service principal") + create_service_principal_cmd = f"az ad sp create-for-rbac" - print(f"Finished to create service principal: {result}") + result = run_command(create_service_principal_cmd, + "--name", service_principal_name) - return result + log(f"Finished creating service principal: {result}") + + return (result["appId"], result["password"], result["tenant"]) -def create_custom_role(custom_role_name, custom_role_json_local_path, subscription): +def get_or_create_custom_role(custom_role_name, custom_role_json_local_path, subscription): """ - Creates a custom role with the given parameters. + Creates a custom role with the given parameters if the role does not already exist. Args: custom_role_name (str): The name of the custom role. @@ -302,17 +395,42 @@ def create_custom_role(custom_role_name, custom_role_json_local_path, subscripti Raises: KeyError: If the 'roleName' key is not present in the response from the 'create_custom_role_cmd' command. """ - print("Create custom role") - custom_role = build_custom_role( - custom_role_name, custom_role_json_local_path, subscription - ) - create_custom_role_cmd = "az role definition create" - create_custom_role_response = run_command( - create_custom_role_cmd, "--role-definition", custom_role + get_custom_role_cmd = f'az role definition list --custom-role-only true' + get_custom_role_response = run_command( + get_custom_role_cmd, + "--scope", f"subscriptions/{subscription}", + "--name", custom_role_name ) + role_already_exists = get_custom_role_response is not None and len(get_custom_role_response) > 0 - result = create_custom_role_response["roleName"] - print(f"Finished to create custom role: {result}") + if not role_already_exists: + custom_role = build_custom_role( + custom_role_name, custom_role_json_local_path, subscription + ) + + # the spot core product custom role is defined in the REST API format (slightly different from the CLI format) + # so use the az rest command + custom_role_id = uuid.uuid4() + create_custom_role_cmd = "az rest --method put" + create_custom_role_response = run_command( + create_custom_role_cmd, + "--url", f"https://management.azure.com/subscriptions/{subscription}/providers/Microsoft.Authorization/roleDefinitions/{custom_role_id}?api-version=2022-04-01", + "--body", custom_role + ) + result = create_custom_role_response["properties"]["roleName"] + + ''' If using a custom role in the CLI format, use the `az role definition create` command + create_custom_role_cmd = "az role definition create" + create_custom_role_response = run_command( + create_custom_role_cmd, "--role-definition", custom_role + ) + result = create_custom_role_response["roleName"] + ''' + + log(f"Finished to create custom role: {result}") + else: + result = get_custom_role_response[0]["roleName"] + log(f"Found already existing custom role: {result}") return result @@ -330,11 +448,23 @@ def login_to_azure(): Returns: None """ - print("Please login to azure (Web page should be opened)") + log("Please login to azure (Web page should be opened)") azure_login_cmd = "az login" run_command(azure_login_cmd) +def ensure_azure_cli_automatic_extension_install_enabled(): + """ + Some of the Azure CLI commands used by this script require az cli extensions. + If an extended command is run without extensions installed, the cli will prompt + to install the extension, getting in the way of the script. This configuration + lets the script proceed without prompting, and automatically installs the required + extension. + """ + run_command('az config set', + 'extension.use_dynamic_install=yes_without_prompt') + + def check_azure_cli_installed(): """ Checks if the Azure CLI is installed by running the 'az' command. @@ -344,7 +474,7 @@ def check_azure_cli_installed(): """ az_cmd = "az" exit_code = subprocess.call( - az_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + az_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=run_in_shell ) if exit_code != 0: raise Exception( @@ -352,61 +482,198 @@ def check_azure_cli_installed(): ) -def main(): +def register_products(spot_account_id, token, products): + """ + Enrolls the spot account in the specified Spot products by communicating with the Spot api. + Args: + spot_account_id: The Spot account to enroll products for. + token: The Spot api token. + products: A string representing which products to enroll in. + """ + session = SpotinstSession(auth_token=token, base_url=SPOT_API_BASE_URL) + client = Client(session=session.session) + if Products.COST_INTELLIGENCE in products: + log(f"Enrolling Spot Account {spot_account_id} in Cost Intelligence.") + + client.send_post(url=SPOT_SETUP_CI_PATH, + body=json.dumps({"account": {"accountId": spot_account_id}}), + entity_name="cost-intelligence-enrollment") + + +def parse_args(): + """ + Retrieve the command line arguments. + """ parser = argparse.ArgumentParser( description="Create required parameters for Spot registration" ) - parser.add_argument("--subscription", required=True, help="Azure Subscription ID") - parser.add_argument("--token", required=True, help="Spot Organization Token") - parser.add_argument("--customRoleName", required=True, help="Custom Role Name") - parser.add_argument( - "--customRoleJsonPath", required=False, help="Custom Role Json File Path" - ) - parser.add_argument( - "--appRegistrationId", required=False, help="Existing App Registration ID" - ) + + # Target Azure subscriptions + parser.add_argument("--subscription", required=False, help="Azure Subscription ID to connect to Spot account. If unspecified all subscriptions in current tenant will be onboarded.") + parser.add_argument("--subscriptionFileName", required=False, help="File path to txt list of subscriptions to connect to Spot accounts.") + + # Spot configuration + parser.add_argument("--token", required=True, help="Spot organization token.") + parser.add_argument("--products", required=False, help="Products to register (e.g. core, cost-intelligence, spot-security, etc.)") + + # Azure resource configuration + parser.add_argument("--skipResourceCreation", default=False, action="store_true", help="Skip creating any resources in Azure. Only creates Spot resources.") + parser.add_argument("--customRoleName", default="Spot-CoreRole", required=False, help="Name to use for the custom role.") + parser.add_argument("--customRoleJsonPath", required=False, help="Custom role Json file path. If specified will not use the default role.") + parser.add_argument("--appRegistrationId", required=False, help="Existing App Registration ID. If specified will not create a new app registration.") + parser.add_argument("--clientSecret", required=False, help="Client secret for the app registration. If specified will not create a new client secret.") + + # other options + parser.add_argument('--shell', default=False, action='store_true', + help="The command will be executed through the shell. May be helpful if seeing errors when running in Windows.") + args = parser.parse_args() - subscription = args.subscription - token = args.token - custom_role_name = args.customRoleName + # custom argument validation + if args.skipResourceCreation and not (args.appRegistrationId and args.clientSecret): + parser.error("Argument --skipResourceCreation requires --appRegistrationId and --clientSecret to be specified.") + + return args + + +def main(): + # command line arguments + args = parse_args() + + # shell configuration + global run_in_shell + run_in_shell = args.shell + + # assign args + if args.skipResourceCreation: + log("Argument `--skipResourceCreation` specified, no Azure resources will be created.") + + subscription_id = args.subscription + if subscription_id is None: + log("Optional argument `--subscription` not specified, looking for txt list or will default to tenant scope.") + if args.subscriptionFileName != "": + subscription_id_list_file_name = args.subscriptionFileName + log("will look for txt file in local path and try to open") + + if subscription_id is None: + log("Optional argument `--subscription` not specified, defaulting to tenant scope.") + + products = args.products + if products is None: + log("Optional argument `--product` not specified, defaulting to `core` product.") + products = f"{Products.CORE}" + + custom_role_name_base = args.customRoleName custom_role_json_local_path = args.customRoleJsonPath - if custom_role_json_local_path is None: - print("WARNING: --customRoleJsonPath not found, using default.") + if not args.skipResourceCreation and custom_role_json_local_path is None: + log("Optional argument `--customRoleJsonPath` not specified, using recommended custom role definition.") - print(f"Start creating required credentials parameters for Spot registration. Subscription: {subscription}") + token = args.token + app_registration_id = args.appRegistrationId + client_secret = args.clientSecret try: check_azure_cli_installed() - # login_to_azure() - subscription_name = get_subscription_name(subscription) - account_id = create_spot_account(subscription_name, token) - service_principal_name = f"Spot-{subscription_name}-{account_id}".replace(" ", "") - required_parameters_response = ( - create_required_parameters_for_spot_registrations( - subscription, - custom_role_name, - custom_role_json_local_path, - service_principal_name, - args.appRegistrationId, - ) - ) - if account_id is not None and token is not None: - set_azure_credentials( - token, - account_id, - required_parameters_response["appId"], - required_parameters_response["password"], - required_parameters_response["tenant"], - subscription, - ) - display_result(subscription, required_parameters_response) - print("Finished creating required credentials parameters for Spot registration") + # Subscriptions / tenant + tenant_id = get_active_tenant() + if subscription_id: + subscription_ids = [subscription_id] + elif subscription_id_list_file_name: + f = open(subscription_id_list_file_name, 'r') + data = f.read() + subscription_ids = str.split(data,"\n") + f.close() + else: + ensure_azure_cli_automatic_extension_install_enabled() + subscription_ids = get_all_subscriptions_in_tenant() + log(f"Found {len(subscription_ids)} subscriptions in tenant.") + + # App registration + if not args.skipResourceCreation: + service_principal_name = "Spot-App" + should_create_app = args.appRegistrationId is None + if should_create_app: + # create the app registration + (app_registration_id, client_secret, tenant) = create_service_principal(service_principal_name) + else: + # if the client secret was not supplied, create a new one + should_reset_client_secret = client_secret is None + if should_reset_client_secret: + (app_registration_id, client_secret, tenant) = create_client_secret(app_registration_id, service_principal_name) + + number_successfully_onboarded = 0 + failed_subscriptions = [] + + # process each subscription specified + for index, subscription_id in enumerate(subscription_ids): + try: + subscription_name = get_subscription_name(subscription_id) + log(f"Onboarding subscription {index+1} of {len(subscription_ids)} ({subscription_name} - {subscription_id})") + + account_id = get_or_create_spot_account(subscription_id, subscription_name, token) + + # only create Azure resources (app registration, client secret, custom role, et al.) if specified + if not args.skipResourceCreation: + custom_role_name = f"{custom_role_name_base}_{account_id}" + roles = get_roles_for_products(products, custom_role_name, custom_role_json_local_path, subscription_id) + + # assign the roles to the service principal + assign_roles(app_registration_id, roles, subscription_id) + + # set the credentials in Spot + set_azure_credentials( + token, + account_id, + app_registration_id, + client_secret, + tenant_id, + subscription_id, + ) + + # register the selected products in Spot + register_products(account_id, token, products) + + if not args.clientSecret: + display_app_result(subscription_id, tenant_id, app_registration_id, client_secret) + + log(f"Completed onboarding subscription {index+1} of {len(subscription_ids)} ({subscription_name} - {subscription_id})\n") + number_successfully_onboarded += 1 + + except SpotinstClientException as e: + # the spotinst token is invalid. halt all onboarding + log(f"Spot client exception. Error: {str(e)}") + failed_subscriptions.append((subscription_id, subscription_name)) + break + except ConnectTimeout as e: + # the Spot API cannot be reached. halt all onboarding + log(f"Spot client connection has timed out. Error: {str(e)}") + failed_subscriptions.append((subscription_id, subscription_name)) + break + except Exception as e: + ''' + a non-transaction breaking exception occurred. stop processing this subscription but continue if + there are more subscriptions to be processed + ''' + log( + f"Error occurred during onboarding process for subscription {subscription_name} - {subscription_id}. Reason: {str(e)}", + LogLevel.ERROR, + ) + failed_subscriptions.append((subscription_id, subscription_name)) + continue + + # print the summary here + log("Operation completed. Summary:") + log(f" Number of subscriptions attempted: {len(subscription_ids)}") + log(f" Number of subscriptions onboarded successfully: {number_successfully_onboarded}") + log(f" Number of subscriptions onboard was unsuccessful: {len(failed_subscriptions)}") + if len(failed_subscriptions) > 0: + log(f" List of subscriptions onboard was unsuccessful:") + for subscription_name, subscription_id in failed_subscriptions: + log(f" {subscription_name} - {subscription_id}") except Exception as e: - print(f"Failed to create required credentials. Errors: {e}") - + log(f"A general error occurred during onboarding. Errors: {e}", LogLevel.ERROR) if __name__ == "__main__": main() diff --git a/Utilities/AzureOnboardingCLI/requirements.txt b/Utilities/AzureOnboardingCLI/requirements.txt index 9fcbdf3..1cd86c1 100644 --- a/Utilities/AzureOnboardingCLI/requirements.txt +++ b/Utilities/AzureOnboardingCLI/requirements.txt @@ -6,7 +6,7 @@ azure-mgmt-subscription==3.1.1 certifi==2023.7.22 cffi==1.15.1 charset-normalizer==3.2.0 -cryptography==41.0.3 +cryptography==41.0.4 idna==3.4 isodate==0.6.1 msal==1.22.0 @@ -22,4 +22,5 @@ requests-oauthlib==1.3.1 six==1.16.0 spotinst-sdk2==2.1.38 typing_extensions==4.7.1 -urllib3==2.0.3 +urllib3==2.0.6 +uuid==1.30 diff --git a/Utilities/AzureSetup/AzureSetup.postman_collection.json b/Utilities/AzureSetup/AzureSetup.postman_collection.json new file mode 100644 index 0000000..d42b59c --- /dev/null +++ b/Utilities/AzureSetup/AzureSetup.postman_collection.json @@ -0,0 +1,464 @@ +{ + "info": { + "_postman_id": "92aa972e-8f3a-4984-84fd-84c1c5ca4d4a", + "name": "Eco Azure Onboarding", + "description": "The list of APIs Rest Endpoints for completing onboarding to the Eco Azure product. Includes tasks like the following:\n\n- Create an App Registration\n \n- Create an App Registration Secret\n \n- Create an App Registration Service Principal\n \n- Create Role Assignments to the Registered App\n \n - Role for Reading Reservations\n \n - Role for Reading Savings Plans\n \n - Role for Tenanat Cost and Billing Reader\n \n - Role for Billing account Read (for EA customers)\n \n - Role for Billing profiles Read (for MCA customers)\n \n - Role for managing existing Reservations (for paying customers)\n \n - Role for managing existing Savings Plans (for paying customers)\n \n - Role for purchasing new Reservations (for paying customers)\n \n - Role for purchasing new Savings Plans (for paying customers)", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "2995178" + }, + "item": [ + { + "name": "Create App Registration", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{graph_bearer_token}}" + } + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"displayName\": \"{{app_display_name}}\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{graph_api}}/v1.0/applications" + }, + "response": [] + }, + { + "name": "Create Service Principal", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{graph_bearer_token}}" + } + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\"appId\": \"{{app_id}}\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{graph_api}}/v1.0/servicePrincipals" + }, + "response": [] + }, + { + "name": "Create Secret Key", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{graph_bearer_token}}" + } + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\"passwordCredential\": {}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{graph_api}}/v1.0/applications(appId='{{app_id}}')/addPassword" + }, + "response": [] + }, + { + "name": "Assign Cost Management Reader", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleDefinitions/72fafb9e-0641-4937-9268-a91bfd8191a3\",\n \"principalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2022-04-01", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Management", + "managementGroups", + "{{tenant_id}}", + "providers", + "Microsoft.Authorization", + "roleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2022-04-01" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Reservation Reader", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Capacity/providers/Microsoft.Authorization/roleDefinitions/582fc458-8989-419f-a480-75249bc5db7e\",\n \"principalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Capacity/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2022-04-01", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Capacity", + "providers", + "Microsoft.Authorization", + "roleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2022-04-01" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Reservation Purchaser (Paying Customers Only)", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleDefinitions/f7b75c60-3036-4b75-91c3-6b41c27c1689\",\n \"principalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2022-04-01", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Management", + "managementGroups", + "{{tenant_id}}", + "providers", + "Microsoft.Authorization", + "roleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2022-04-01" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Savings Plan Purchaser (Paying Customers Only)", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleDefinitions/3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74\",\n \"principalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Management/managementGroups/{{tenant_id}}/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2022-04-01", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Management", + "managementGroups", + "{{tenant_id}}", + "providers", + "Microsoft.Authorization", + "roleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2022-04-01" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Billing Reader (MCA and CSP Customers Only)", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"Properties\": {\n \"RoleDefinitionId\": \"providers/Microsoft.Billing/billingAccounts/{{billing_account_id}}/billingRoleDefinitions/50000000-aaaa-bbbb-cccc-100000000002\",\n \"PrincipalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Billing/billingAccounts/{{billing_account_id}}/createBillingRoleAssignment?api-version=2019-10-01-preview", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Billing", + "billingAccounts", + "{{billing_account_id}}", + "createBillingRoleAssignment" + ], + "query": [ + { + "key": "api-version", + "value": "2019-10-01-preview" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Enrollment Reader (EA Customers Only)", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Billing/billingAccounts/{{billing_account_id}}/billingRoleDefinitions/24f8edb6-1668-4659-b5e2-40bb5f3a7d7e\",\n \"principalId\": \"{{service_principal_id}}\",\n \"principalTenantId\": \"{{tenant_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Billing/billingAccounts/{{billing_account_id}}/billingRoleAssignments/{{$guid}}?api-version=2019-10-01-preview", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Billing", + "billingAccounts", + "{{billing_account_id}}", + "billingRoleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2019-10-01-preview" + } + ] + } + }, + "response": [] + }, + { + "name": "Assign Reservation Administrator (Paying Customers Only)", + "request": { + "auth": { + "type": "bearer", + "bearer": { + "token": "{{management_bearer_token}}" + } + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"properties\": {\n \"roleDefinitionId\": \"providers/Microsoft.Capacity/providers/Microsoft.Authorization/roleDefinitions/a8889054-8d42-49c9-bc1c-52486c10e7cd\",\n \"principalId\": \"{{service_principal_id}}\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{management_api}}/providers/Microsoft.Capacity/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2022-04-01", + "host": [ + "{{management_api}}" + ], + "path": [ + "providers", + "Microsoft.Capacity", + "providers", + "Microsoft.Authorization", + "roleAssignments", + "{{$guid}}" + ], + "query": [ + { + "key": "api-version", + "value": "2022-04-01" + } + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "management_bearer_token", + "value": "", + "type": "string" + }, + { + "key": "graph_bearer_token", + "value": "", + "type": "string" + }, + { + "key": "graph_api", + "value": "https://graph.microsoft.com", + "type": "string" + }, + { + "key": "management_api", + "value": "https://management.azure.com", + "type": "string" + }, + { + "key": "app_display_name", + "value": "EcoAzureConnection", + "type": "string" + }, + { + "key": "app_id", + "value": "", + "type": "string" + }, + { + "key": "service_principal_id", + "value": "", + "type": "string" + }, + { + "key": "tenant_id", + "value": "", + "type": "string" + }, + { + "key": "billing_account_id", + "value": "", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/Utilities/AzureSetup/CSP/eco_azure_full_access.ps1 b/Utilities/AzureSetup/CSP/eco_azure_full_access.ps1 new file mode 100644 index 0000000..bcb6018 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_full_access.ps1 @@ -0,0 +1,70 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign reservation administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Administrator" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign savings plan administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Administrator" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign billing reader role via REST API +$ROLE_DEF_ID = "50000000-aaaa-bbbb-cccc-100000000002" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + Properties = @{ + RoleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + PrincipalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Post ` + -Uri "https://management.azure.com/$SCOPE/createBillingRoleAssignment?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/CSP/eco_azure_full_access.py b/Utilities/AzureSetup/CSP/eco_azure_full_access.py new file mode 100644 index 0000000..44b22f3 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_full_access.py @@ -0,0 +1,133 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation purchaser role +role_definition_id = "f7b75c60-3036-4b75-91c3-6b41c27c1689" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation administrator role +role_definition_id = "a8889054-8d42-49c9-bc1c-52486c10e7cd" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan purchaser role +role_definition_id = "3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan administrator role +role_definition_id = "433febaf-a31d-4d4f-8dc8-b4593b39bda5" +scope = "providers/Microsoft.BillingBenefits" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign billing reader role +role_definition_id = "50000000-aaaa-bbbb-cccc-100000000002" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/createBillingRoleAssignment?api-version=2019-10-01-preview" +data = { + "Properties": { + "RoleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "PrincipalId": object_id + } +} +resp = requests.post(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/CSP/eco_azure_full_access.sh b/Utilities/AzureSetup/CSP/eco_azure_full_access.sh new file mode 100644 index 0000000..b9965da --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_full_access.sh @@ -0,0 +1,55 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +az role assignment create --assignee $APP_ID --role "Reservations Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign reservation administrator role +az role assignment create --assignee $APP_ID --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +az role assignment create --assignee $APP_ID --role "Savings plan Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign savings plan administrator role +az role assignment create --assignee $APP_ID --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Billing Reader role using REST API +ROLE_DEF_ID="50000000-aaaa-bbbb-cccc-100000000002" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"Properties\": {\"RoleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"PrincipalId\": \"${PRINCIPAL_ID}\"}}' +curl -X POST \ + "https://management.azure.com/${SCOPE}/createBillingRoleAssignment?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/CSP/eco_azure_full_access.tf b/Utilities/AzureSetup/CSP/eco_azure_full_access.tf new file mode 100644 index 0000000..a1fcddb --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_full_access.tf @@ -0,0 +1,103 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign reservation purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Reservations Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign reservation administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Savings plan Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign savings plan administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign billing reader role +resource "null_resource" "billing_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Billing Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/CSP/eco_azure_readonly.ps1 b/Utilities/AzureSetup/CSP/eco_azure_readonly.ps1 new file mode 100644 index 0000000..4aec719 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_readonly.ps1 @@ -0,0 +1,57 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign billing reader role via REST API +$ROLE_DEF_ID = "50000000-aaaa-bbbb-cccc-100000000002" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + Properties = @{ + RoleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + PrincipalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Post ` + -Uri "https://management.azure.com/$SCOPE/createBillingRoleAssignment?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/CSP/eco_azure_readonly.py b/Utilities/AzureSetup/CSP/eco_azure_readonly.py new file mode 100644 index 0000000..4cda430 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_readonly.py @@ -0,0 +1,83 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +from Utilities.AzureSetup.CSP.eco_azure_full_access import BILLING_ACCOUNT_ID + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign billing reader role +role_definition_id = "50000000-aaaa-bbbb-cccc-100000000002" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/createBillingRoleAssignment?api-version=2019-10-01-preview" +data = { + "Properties": { + "RoleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "PrincipalId": object_id + } +} +resp = requests.post(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/CSP/eco_azure_readonly.sh b/Utilities/AzureSetup/CSP/eco_azure_readonly.sh new file mode 100644 index 0000000..1002ee8 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_readonly.sh @@ -0,0 +1,43 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Billing Reader role using REST API +ROLE_DEF_ID="50000000-aaaa-bbbb-cccc-100000000002" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"Properties\": {\"RoleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"PrincipalId\": \"${PRINCIPAL_ID}\"}}' +curl -X POST \ + "https://management.azure.com/${SCOPE}/createBillingRoleAssignment?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/CSP/eco_azure_readonly.tf b/Utilities/AzureSetup/CSP/eco_azure_readonly.tf new file mode 100644 index 0000000..c6c5755 --- /dev/null +++ b/Utilities/AzureSetup/CSP/eco_azure_readonly.tf @@ -0,0 +1,75 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign billing reader role +resource "null_resource" "billing_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Billing Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/EA/eco_azure_full_access.ps1 b/Utilities/AzureSetup/EA/eco_azure_full_access.ps1 new file mode 100644 index 0000000..56815f3 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_full_access.ps1 @@ -0,0 +1,72 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign reservation administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Administrator" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign savings plan administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Administrator" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# Generate a UUID for the role assignment ID +$ROLE_ASSIGNMENT_ID = [guid]::NewGuid().ToString().ToLower() +$ROLE_DEF_ID = "24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + properties = @{ + roleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + principalTenantId = $tenantId + principalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Put ` + -Uri "https://management.azure.com/$SCOPE/billingRoleAssignments/$ROLE_ASSIGNMENT_ID?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/EA/eco_azure_full_access.py b/Utilities/AzureSetup/EA/eco_azure_full_access.py new file mode 100644 index 0000000..90c3537 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_full_access.py @@ -0,0 +1,133 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation purchaser role +role_definition_id = "f7b75c60-3036-4b75-91c3-6b41c27c1689" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation administrator role +role_definition_id = "a8889054-8d42-49c9-bc1c-52486c10e7cd" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan purchaser role +role_definition_id = "3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan administrator role +role_definition_id = "433febaf-a31d-4d4f-8dc8-b4593b39bda5" +scope = "providers/Microsoft.BillingBenefits" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign enrollment reader role +role_definition_id = "24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/billingRoleAssignments/{str(uuid.uuid4())}?api-version=2019-10-01-preview" +data = { + "properties": { + "roleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "principalId": object_id, + "principalTenantId": TENANT_ID, + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/EA/eco_azure_full_access.sh b/Utilities/AzureSetup/EA/eco_azure_full_access.sh new file mode 100644 index 0000000..3ee88a2 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_full_access.sh @@ -0,0 +1,57 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +az role assignment create --assignee $APP_ID --role "Reservations Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign reservation administrator role +az role assignment create --assignee $APP_ID --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +az role assignment create --assignee $APP_ID --role "Savings plan Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign savings plan administrator role +az role assignment create --assignee $APP_ID --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Enrollment Reader role using REST API +ROLE_ASSIGNMENT_ID=$(uuidgen | tr 'A-F' 'a-f') +ROLE_DEF_ID="24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"properties\": {\"roleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"principalTenantId\": \"${TENANT_ID}\", \"principalId\": \"${PRINCIPAL_ID}\"}}' +curl -X PUT \ + "https://management.azure.com/${SCOPE}/billingRoleAssignments/${ROLE_ASSIGNMENT_ID}?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/EA/eco_azure_full_access.tf b/Utilities/AzureSetup/EA/eco_azure_full_access.tf new file mode 100644 index 0000000..96aab5a --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_full_access.tf @@ -0,0 +1,103 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign reservation purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Reservations Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign reservation administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign savings plan purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Savings plan Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign savings plan administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign enrollment reader role +resource "null_resource" "enrollment_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Enrollment Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/EA/eco_azure_readonly.ps1 b/Utilities/AzureSetup/EA/eco_azure_readonly.ps1 new file mode 100644 index 0000000..f2ad388 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_readonly.ps1 @@ -0,0 +1,60 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# Generate a UUID for the role assignment ID +$ROLE_ASSIGNMENT_ID = [guid]::NewGuid().ToString().ToLower() +$ROLE_DEF_ID = "24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + properties = @{ + roleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + principalTenantId = $tenantId + principalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Put ` + -Uri "https://management.azure.com/$SCOPE/billingRoleAssignments/$ROLE_ASSIGNMENT_ID?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/EA/eco_azure_readonly.py b/Utilities/AzureSetup/EA/eco_azure_readonly.py new file mode 100644 index 0000000..45f3c93 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_readonly.py @@ -0,0 +1,82 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign enrollment reader role +role_definition_id = "24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/billingRoleAssignments/{str(uuid.uuid4())}?api-version=2019-10-01-preview" +data = { + "properties": { + "roleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "principalId": object_id, + "principalTenantId": TENANT_ID, + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/EA/eco_azure_readonly.sh b/Utilities/AzureSetup/EA/eco_azure_readonly.sh new file mode 100644 index 0000000..8606866 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_readonly.sh @@ -0,0 +1,44 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee "{{app_id}}" --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee "{{app_id}}" --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Enrollment Reader role using REST API +ROLE_ASSIGNMENT_ID=$(uuidgen | tr 'A-F' 'a-f') +ROLE_DEF_ID="24f8edb6-1668-4659-b5e2-40bb5f3a7d7e" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"properties\": {\"roleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"principalTenantId\": \"${TENANT_ID}\", \"principalId\": \"${PRINCIPAL_ID}\"}}' +curl -X PUT \ + "https://management.azure.com/${SCOPE}/billingRoleAssignments/${ROLE_ASSIGNMENT_ID}?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/EA/eco_azure_readonly.tf b/Utilities/AzureSetup/EA/eco_azure_readonly.tf new file mode 100644 index 0000000..12b8c75 --- /dev/null +++ b/Utilities/AzureSetup/EA/eco_azure_readonly.tf @@ -0,0 +1,75 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign enrollment reader role +resource "null_resource" "enrollment_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Enrollment Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/MCA/eco_azure_full_access.ps1 b/Utilities/AzureSetup/MCA/eco_azure_full_access.ps1 new file mode 100644 index 0000000..bcb6018 --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_full_access.ps1 @@ -0,0 +1,70 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign reservation administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Administrator" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign savings plan administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Administrator" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign billing reader role via REST API +$ROLE_DEF_ID = "50000000-aaaa-bbbb-cccc-100000000002" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + Properties = @{ + RoleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + PrincipalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Post ` + -Uri "https://management.azure.com/$SCOPE/createBillingRoleAssignment?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/MCA/eco_azure_full_access.py b/Utilities/AzureSetup/MCA/eco_azure_full_access.py new file mode 100644 index 0000000..44b22f3 --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_full_access.py @@ -0,0 +1,133 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation purchaser role +role_definition_id = "f7b75c60-3036-4b75-91c3-6b41c27c1689" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation administrator role +role_definition_id = "a8889054-8d42-49c9-bc1c-52486c10e7cd" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan purchaser role +role_definition_id = "3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan administrator role +role_definition_id = "433febaf-a31d-4d4f-8dc8-b4593b39bda5" +scope = "providers/Microsoft.BillingBenefits" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign billing reader role +role_definition_id = "50000000-aaaa-bbbb-cccc-100000000002" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/createBillingRoleAssignment?api-version=2019-10-01-preview" +data = { + "Properties": { + "RoleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "PrincipalId": object_id + } +} +resp = requests.post(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/MCA/eco_azure_full_access.sh b/Utilities/AzureSetup/MCA/eco_azure_full_access.sh new file mode 100644 index 0000000..b9965da --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_full_access.sh @@ -0,0 +1,55 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +az role assignment create --assignee $APP_ID --role "Reservations Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign reservation administrator role +az role assignment create --assignee $APP_ID --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +az role assignment create --assignee $APP_ID --role "Savings plan Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign savings plan administrator role +az role assignment create --assignee $APP_ID --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Billing Reader role using REST API +ROLE_DEF_ID="50000000-aaaa-bbbb-cccc-100000000002" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"Properties\": {\"RoleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"PrincipalId\": \"${PRINCIPAL_ID}\"}}' +curl -X POST \ + "https://management.azure.com/${SCOPE}/createBillingRoleAssignment?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/MCA/eco_azure_full_access.tf b/Utilities/AzureSetup/MCA/eco_azure_full_access.tf new file mode 100644 index 0000000..1de044d --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_full_access.tf @@ -0,0 +1,103 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign reservation purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Reservations Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign reservation administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign savings plan purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Savings plan Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign savings plan administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign billing reader role +resource "null_resource" "billing_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Billing Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/MCA/eco_azure_readonly.ps1 b/Utilities/AzureSetup/MCA/eco_azure_readonly.ps1 new file mode 100644 index 0000000..55e5a0c --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_readonly.ps1 @@ -0,0 +1,58 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal and get its ID +$sp = New-AzADServicePrincipal -ApplicationId $app.AppId +$principalId = $sp.Id + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign billing reader role via REST API +$ROLE_DEF_ID = "50000000-aaaa-bbbb-cccc-100000000002" +$API_VERSION = "2019-10-01-preview" +$SCOPE = "providers/Microsoft.Billing/billingAccounts/$billingAccountId" +$ACCESS_TOKEN = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv +$DATA = @{ + Properties = @{ + RoleDefinitionId = "/$SCOPE/billingRoleDefinitions/$ROLE_DEF_ID" + PrincipalId = $principalId + } +} | ConvertTo-Json +$headers = @{ + "Content-Type" = "application/json" + "Authorization" = "Bearer $ACCESS_TOKEN" +} +Invoke-RestMethod -Method Post ` + -Uri "https://management.azure.com/$SCOPE/createBillingRoleAssignment?api-version=$API_VERSION" ` + -Headers $headers ` + -Body $DATA + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/MCA/eco_azure_readonly.py b/Utilities/AzureSetup/MCA/eco_azure_readonly.py new file mode 100644 index 0000000..ecb3c28 --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_readonly.py @@ -0,0 +1,81 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" +BILLING_ACCOUNT_ID = "{{billing_account_id}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign billing reader role +role_definition_id = "50000000-aaaa-bbbb-cccc-100000000002" +scope = f"providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/createBillingRoleAssignment?api-version=2019-10-01-preview" +data = { + "Properties": { + "RoleDefinitionId": f"{scope}/billingRoleDefinitions/{role_definition_id}", + "PrincipalId": object_id + } +} +resp = requests.post(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/MCA/eco_azure_readonly.sh b/Utilities/AzureSetup/MCA/eco_azure_readonly.sh new file mode 100644 index 0000000..1002ee8 --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_readonly.sh @@ -0,0 +1,43 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" +BILLING_ACCOUNT_ID="{{billing_account_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +PRINCIPAL_ID=$(az ad sp create --id $APP_ID --output json --query id | tr -d '"') + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign Billing Reader role using REST API +ROLE_DEF_ID="50000000-aaaa-bbbb-cccc-100000000002" +API_VERSION="2019-10-01-preview" +SCOPE="providers/Microsoft.Billing/billingAccounts/${BILLING_ACCOUNT_ID}" +ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv) +DATA='{\"Properties\": {\"RoleDefinitionId\": \"/${SCOPE}/billingRoleDefinitions/${ROLE_DEF_ID}\", \"PrincipalId\": \"${PRINCIPAL_ID}\"}}' +curl -X POST \ + "https://management.azure.com/${SCOPE}/createBillingRoleAssignment?api-version=${API_VERSION}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "${DATA}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/MCA/eco_azure_readonly.tf b/Utilities/AzureSetup/MCA/eco_azure_readonly.tf new file mode 100644 index 0000000..c6c5755 --- /dev/null +++ b/Utilities/AzureSetup/MCA/eco_azure_readonly.tf @@ -0,0 +1,75 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign billing reader role +resource "null_resource" "billing_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Billing Reader" --scope "/providers/Microsoft.Billing/billingAccounts/${var.billing_account_id}"' + } +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/PAYG/eco_azure_full_access.ps1 b/Utilities/AzureSetup/PAYG/eco_azure_full_access.ps1 new file mode 100644 index 0000000..1f06d75 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_full_access.ps1 @@ -0,0 +1,48 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal +New-AzADServicePrincipal -ApplicationId $app.AppId + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign reservation administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Administrator" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Purchaser" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + +# assign savings plan administrator role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Administrator" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/PAYG/eco_azure_full_access.py b/Utilities/AzureSetup/PAYG/eco_azure_full_access.py new file mode 100644 index 0000000..2d22290 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_full_access.py @@ -0,0 +1,119 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation purchaser role +role_definition_id = "f7b75c60-3036-4b75-91c3-6b41c27c1689" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign reservation administrator role +role_definition_id = "a8889054-8d42-49c9-bc1c-52486c10e7cd" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan purchaser role +role_definition_id = "3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign savings plan administrator role +role_definition_id = "433febaf-a31d-4d4f-8dc8-b4593b39bda5" +scope = "providers/Microsoft.BillingBenefits" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/PAYG/eco_azure_full_access.sh b/Utilities/AzureSetup/PAYG/eco_azure_full_access.sh new file mode 100644 index 0000000..bc303e6 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_full_access.sh @@ -0,0 +1,42 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +az ad sp create --id $APP_ID + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign reservation purchaser role +az role assignment create --assignee $APP_ID --role "Reservations Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign reservation administrator role +az role assignment create --assignee $APP_ID --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign savings plan purchaser role +az role assignment create --assignee $APP_ID --role "Savings plan Purchaser" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + +# assign savings plan administrator role +az role assignment create --assignee $APP_ID --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/PAYG/eco_azure_full_access.tf b/Utilities/AzureSetup/PAYG/eco_azure_full_access.tf new file mode 100644 index 0000000..42b86c7 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_full_access.tf @@ -0,0 +1,96 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign reservation purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Reservations Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign reservation administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Administrator" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign savings plan purchaser role +resource "azurerm_role_assignment" "reservation_purchaser_role_assignment" { + role_definition_name = "Savings plan Purchaser" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# assign savings plan administrator role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Administrator" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/PAYG/eco_azure_readonly.ps1 b/Utilities/AzureSetup/PAYG/eco_azure_readonly.ps1 new file mode 100644 index 0000000..1fda6cd --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_readonly.ps1 @@ -0,0 +1,36 @@ +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" + +# Connect to Azure AD +Connect-AzAccount -TenantId $tenantId + +# Register the app +$app = New-AzADApplication -DisplayName $appName + +# Create a service principal +New-AzADServicePrincipal -ApplicationId $app.AppId + +# Delete all secret keys +$secretKeys = Get-AzADAppCredential -ApplicationId $appId +foreach ($secretKey in $secretKeys) { + # Remove the secret key + Remove-AzADAppCredential -ApplicationId $appId -KeyId $secretKey.KeyId +} + +# Create a client secret +$secret = New-AzADAppCredential -ApplicationId $app.AppId -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1) + +# Role assignments +# assign reservation reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +New-AzRoleAssignment -ApplicationId $appId -RoleDefinitionName "Cost Management Reader" -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" + + +# Output app details +Write-Host "App ID:" $app.AppId +Write-Host "Secret Key:" $secret.SecretText diff --git a/Utilities/AzureSetup/PAYG/eco_azure_readonly.py b/Utilities/AzureSetup/PAYG/eco_azure_readonly.py new file mode 100644 index 0000000..b05ff46 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_readonly.py @@ -0,0 +1,67 @@ +import requests +from azure.identity import DefaultAzureCredential +import uuid + +# Set up the necessary variables +TENANT_ID = "{{tenant_id}}" +APP_NAME = "{{app_name}}" + +# Authenticate using DefaultAzureCredential +credential = DefaultAzureCredential() +graph_token = credential.get_token("https://graph.microsoft.com/.default") +management_token = credential.get_token("https://management.azure.com/.default") +graph_header = {"Authorization": f"Bearer {graph_token.token}", "Content-Type": "application/json"} +management_header = {"Authorization": f"Bearer {management_token.token}", "Content-Type": "application/json"} + +# Register the application +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications", json={"displayName": APP_NAME}) +app_id = resp.json()["appId"] + +# Remove the existing secret keys +resp = requests.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')", headers=graph_header) +resp.raise_for_status() +keys = [pc["keyId"] for pc in resp.json()["passwordCredentials"]] +for key in keys: + delete_response = requests.post(url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/removePassword", headers=graph_header, json={"keyId": key}) + delete_response.raise_for_status() + +# Create the secret key +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')/addPassword", json={"passwordCredential": {}}) +resp.raise_for_status() +secret_key = resp.json()["secretText"] + +# Create the service principal +resp = requests.post(headers=graph_header, url=f"https://graph.microsoft.com/v1.0/servicePrincipals", json={"appId": app_id}) +resp.raise_for_status() +object_id = resp.json()["id"] + +# setup for role assignments +# assign reservation reader role +role_definition_id = "582fc458-8989-419f-a480-75249bc5db7e" +scope = "providers/Microsoft.Capacity" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + +# assign cost management reader role +role_definition_id = "72fafb9e-0641-4937-9268-a91bfd8191a3" +scope = f"providers/Microsoft.Management/managementGroups/{TENANT_ID}" +role_assignments_url = f"https://management.azure.com/{scope}/providers/Microsoft.Authorization/roleAssignments/{str(uuid.uuid4())}?api-version=2022-04-01" +data = { + "properties": { + "roleDefinitionId": f"{scope}/providers/Microsoft.Authorization/roleDefinitions/{role_definition_id}", + "principalId": object_id + } +} +resp = requests.put(url=role_assignments_url, headers=management_header, json=data) +resp.raise_for_status() + + +print(f"App ID: {app_id}") +print(f"Secret Key: {secret_key}") diff --git a/Utilities/AzureSetup/PAYG/eco_azure_readonly.sh b/Utilities/AzureSetup/PAYG/eco_azure_readonly.sh new file mode 100644 index 0000000..6a3340a --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_readonly.sh @@ -0,0 +1,30 @@ +APP_NAME="{{app_name}}" +TENANT_ID="{{tenant_id}}" + +az login --tenant $TENANT_ID + +# Create app +APP_ID=$(az ad app create --display-name $APP_NAME --output json --query appId) +APP_ID=$(echo $APP_ID | tr -d '"') + +# Generate secret key +SECRET_KEY=$(az ad app credential reset --id $APP_ID --output json --query password) +SECRET_KEY=$(echo $SECRET_KEY | tr -d '"') + +# Create service principal +az ad sp create --id $APP_ID + +# Role assignments +# assign reservation reader role +az role assignment create --assignee $APP_ID --role "Reservations Reader" --scope "/providers/Microsoft.Capacity" + +# assign savings plan reader role +az role assignment create --assignee $APP_ID --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits" + +# assign cost management reader role +az role assignment create --assignee $APP_ID --role "Cost Management Reader" --scope "/providers/Microsoft.Management/managementGroups/${TENANT_ID}" + + +# Print registered app info +echo "App ID: $APP_ID" +echo "Secret Key: $SECRET_KEY" diff --git a/Utilities/AzureSetup/PAYG/eco_azure_readonly.tf b/Utilities/AzureSetup/PAYG/eco_azure_readonly.tf new file mode 100644 index 0000000..28faf98 --- /dev/null +++ b/Utilities/AzureSetup/PAYG/eco_azure_readonly.tf @@ -0,0 +1,68 @@ +variable "tenant_id" { + description = "The Tenant ID for the Azure subscription" + type = string + default = "" +} + +variable "billing_account_id" { + description = "The Billing Account ID" + type = string + default = "" +} + +# Provider block +provider "azuread" { + version = "3.0.0" +} +provider "azurerm" { + version = "3.0.0" + features {} +} +# Resource block +# Create an Azure AD application registration +resource "azuread_application_registration" "create_registered_app" { + display_name = "EcoAzureADApp3" +} +# Create an Azure AD service principal +resource "azuread_service_principal" "create_service_principal" { + client_id = azuread_application_registration.create_registered_app.client_id +} +# Create an Azure AD application password +resource "azuread_application_password" "create_secret_key" { + application_id = azuread_application_registration.create_registered_app.id +} + +# Role assignments +# assign reservation reader role +resource "null_resource" "reservation_reader_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Reservations Reader" --scope "/providers/Microsoft.Capacity"' + } +} + +# assign savings plan reader role +resource "null_resource" "reservation_administrator_role_assignment" { + provisioner "local-exec" { + command = 'az role assignment create --assignee ${azuread_application_registration.create_registered_app.id} --role "Savings plan Reader" --scope "/providers/Microsoft.BillingBenefits"' + } +} + +# assign cost management reader role +resource "azurerm_role_assignment" "cost_management_reader_role_assignment" { + role_definition_name = "Cost Management Reader" + principal_id = azuread_service_principal.create_service_principal.id + scope = "/providers/Microsoft.Management/managementGroups/${var.tenant_id}" +} + +# Output +output "client_id" { + value = azuread_application_registration.create_registered_app.client_id +} +output "object_id" { + value = azuread_service_principal.create_service_principal.object_id +} +output "application_secret" { + value = azuread_application_password.create_secret_key.value + sensitive = true +} +data "azuread_client_config" "current" {} diff --git a/Utilities/AzureSetup/README.md b/Utilities/AzureSetup/README.md new file mode 100644 index 0000000..7f088fd --- /dev/null +++ b/Utilities/AzureSetup/README.md @@ -0,0 +1,33 @@ +# Azure Setup Scripts +This repository contains scripts to assist with setting up Eco Azure. The following Azure agreement types are supported: +* Microsoft CSP (Cloud Solution Provider) + * Full Access + * Read Only +* Enterprise Agreement + * Full Access + * Read Only +* Microsoft Customer Agreement (MCA) + * Full Access + * Read Only +* Pay-As-You-Go + * Full Access + * Read Only + +## Usage +1. Chose an appropriate script to run based on permissions level and Azure agreement type. +2. Update the following variables to values appropriate for your environment: +``` +$appName = "{{app_name}}" +$tenantId = "{{tenant_id}}" +$billingAccountId = "{{billing_account_id}}" +``` +3. If using the provided python scripts: install the requirements using pip +``` +pip install -r requirements.txt +``` +4. Execute the script +``` +# For example +python PAYG/eco_azure_full_access.py +``` + diff --git a/Utilities/AzureSetup/requirements.txt b/Utilities/AzureSetup/requirements.txt new file mode 100644 index 0000000..1ecbad7 --- /dev/null +++ b/Utilities/AzureSetup/requirements.txt @@ -0,0 +1,2 @@ +azure-identity==1.13.0 +requests==2.31.0 diff --git a/Utilities/GC/gc-cloudshell-bulk-import/readme.md b/Utilities/GC/gc-cloudshell-bulk-import/readme.md new file mode 100644 index 0000000..6ec8ddc --- /dev/null +++ b/Utilities/GC/gc-cloudshell-bulk-import/readme.md @@ -0,0 +1,59 @@ + +# Spot.io GC Cloudshell Bulk Onboarder + + +Spot.io GC Cloudshell Bulk Onboarder is a bulk project import tool that can be ran directly in the GC console terminal. It creates Spot.io required GC roles, service accounts, a Spot.io token per project and will onboard GC projects to the Spot console using the Spot.io API and gcloud commands. + + +## Prerequisites +* An active Spot.io account is required with a provisioned API admin token. See [here](https://docs.spot.io/administration/api/create-api-token) if you need to setup a token. +* GC Projects must have the Compute Engine API enabled. You can check the status for each project using the following url. https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=#####insert_project_name###### +* A single, comma separated string of GC Projects that needs onboarding. +* GC Admin access to all target projects and access to the GC cloudshell console + +## Limitations and considerations +* There is no rollback feature for this tool. Duplicate assets will be generated if the script is run more than once to onboard the same project. All duplicate assets created in GC will need to be cleaned up manually. +* Once the GC read-only Roles have been deployed; this tool cannot upgrade deployed Role policies to full permissions at a later date. + +## Automation Flow +All assets created by the tool are on a per project basis for security purposes. Each connected project will have a unique Spot.io token restricted to the matching GC service account. + +. Iterate through the input list of projects\ +.. Create GC Service Account\ +... Create and download Service Account Key\ +.... Create Spot.io account\ +..... Create unique programmatic user API token\ +...... Import GC project using the downloaded Service Account Key, Spot.io Acct ID and unique programmatic API user token\ +(loop) + +## Deployment +To deploy this script run the following shell commands + +```bash + wget https://s3-url-tbd/spot_onboarder.sh + chmod +x spot_onboarder.sh +``` + +## Script Usage Example +```bash + + ./spot_onboarder.sh api-token-value csv-list-of-projects false + +``` + +## Logging +The script will generate a lot of log prints. It would be best if you pipe them to a log file for follow-up analysis. + +```bash + + ./spot_onboarder.sh api-token-value csv-list-of-projects false >> spot_onboarder.log + +``` + + +## Input Parameters +* (String) API token +* (String) CSV list of GC Projects +* (boolean) Read-only Role deployment flag - Default: false + + diff --git a/Utilities/GC/gc-cloudshell-bulk-import/spot_onboarder.sh b/Utilities/GC/gc-cloudshell-bulk-import/spot_onboarder.sh new file mode 100755 index 0000000..ff5a982 --- /dev/null +++ b/Utilities/GC/gc-cloudshell-bulk-import/spot_onboarder.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Function to create a service account and download its key; Create and connect GCP project to Spot.io account using a programmatic user token +# Parameters: +# 1. spot_token: The Spot.io token. No default value. +# 2. project_ids: A comma-separated list of project IDs. No default value. +# 3. read_only: A boolean indicating whether the account should be read-only. Defaults to false. +create_service_account() { + local service_account_name="spot-io-$(openssl rand -hex 4)" + local spot_role_name="spot_io_Role_$(openssl rand -hex 4)" + local spot_token=$1 + local project_ids=($(echo $2 | tr "," "\n")) + local read_only=${3:-false} + local ROLE_YML="spotinst_service_account_role.yml" + local ROLE_URI="https://spotinst-public.s3.amazonaws.com/assets/gcp/spotinst-service-role.yaml" + + if [ "$read_only" = true ] ; then + ROLE_URI="https://spot-connect-account-cf.s3.amazonaws.com/spot-gcp-service-role-read-only.yaml" + fi + + # Downloading spotinst-service-role.yml to local folder + curl ${ROLE_URI} -o ${ROLE_YML} + + if [ -z "$spot_token" ]; then + echo "Spot Token: ${spot_token}" + exit -1 + fi + + for project_id in "${project_ids[@]}" + do + # enable service management API + gcloud services enable servicemanagement --project ${project_id} + # create spotinst role from file + gcloud iam roles create ${spot_role_name} --project ${project_id} --file ${ROLE_YML} -q + # create spotinst ServiceAccount + gcloud iam service-accounts create ${service_account_name} --display-name ${service_account_name} --project ${project_id} + # add spotinst role to spotinst service account + gcloud projects add-iam-policy-binding ${project_id} \ + --member serviceAccount:${service_account_name}@${project_id}.iam.gserviceaccount.com \ + --role projects/${project_id}/roles/${spot_role_name} --condition=None + # add serviceAccountUser role to spotinst service account + gcloud projects add-iam-policy-binding ${project_id} \ + --member serviceAccount:${service_account_name}@${project_id}.iam.gserviceaccount.com \ + --role roles/iam.serviceAccountUser --condition=None + # Create and download key for service account + gcloud iam service-accounts keys create spotinst_key.json \ + --iam-account ${service_account_name}@${project_id}.iam.gserviceaccount.com --project ${project_id} + + # Call Spot.io API to set existing user permissions + response=$(curl -X POST "https://api.spotinst.io/setup/account" \ + -H "Authorization: Bearer $spot_token" \ + -H "Content-Type: application/json" \ + -d "{ + \"account\": { + \"name\": \"GCP-$project_id\" + } + }") + + # Extract the account id from the response and set it to new_spot_account_id + new_spot_account_id=$(echo $response | jq -r '.response.items[0].id') + + # Call Spot.io API to create a programmatic user + response=$(curl -X POST "https://api.spotinst.io/setup/user/programmatic" \ + -H "Authorization: Bearer $spot_token" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"GCP-$project_id-$(openssl rand -hex 4)\", + \"accounts\": [ + { + \"id\": \"$new_spot_account_id\", + \"role\": \"editor\" + } + ] + }") + + # Extract the token from the response and update the Spot.io token + programmatic_spot_token=$(echo $response | jq -r '.response.items[0].token') + + # Echo the Spot.io token to the console + echo "The Spot.io token for the programmatic user is: $programmatic_spot_token" + + # Call Spot.io API to setup credentials + service_account_key_data=$(cat spotinst_key.json | jq 'del(.universe_domain)') + curl -X POST "https://api.spotinst.io/gcp/setup/credentials/?accountId=$new_spot_account_id" \ + -H "Authorization: Bearer $programmatic_spot_token" \ + -H "Content-Type: application/json" \ + -d "{ + \"serviceAccount\": $service_account_key_data + }" + done +} + +# Call the function with command line arguments +create_service_account "$@"