github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/aws/identifiers.go (about) 1 /* 2 Copyright 2022 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package aws 18 19 import ( 20 "regexp" 21 "slices" 22 "strings" 23 24 "github.com/gravitational/trace" 25 ) 26 27 // IsValidAccountID checks whether the accountID is a valid AWS Account ID 28 // 29 // https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html 30 func IsValidAccountID(accountID string) error { 31 if len(accountID) != 12 { 32 return trace.BadParameter("must be 12-digit") 33 } 34 for _, d := range accountID { 35 if d < '0' || d > '9' { 36 return trace.BadParameter("must be 12-digit") 37 } 38 } 39 40 return nil 41 } 42 43 // IsValidIAMRoleName checks whether the role name is a valid AWS IAM Role identifier. 44 // 45 // > Length Constraints: Minimum length of 1. Maximum length of 64. 46 // > Pattern: [\w+=,.@-]+ 47 // https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html 48 func IsValidIAMRoleName(roleName string) error { 49 if len(roleName) == 0 || len(roleName) > 64 || !matchRoleName(roleName) { 50 return trace.BadParameter("role is invalid") 51 } 52 53 return nil 54 } 55 56 // IsValidIAMPolicyName checks whether the policy name is a valid AWS IAM Policy 57 // identifier. 58 // 59 // > Length Constraints: Minimum length of 1. Maximum length of 128. 60 // > Pattern: [\w+=,.@-]+ 61 // https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreatePolicy.html 62 func IsValidIAMPolicyName(policyName string) error { 63 // The same regex is used for role and policy names. 64 if len(policyName) == 0 || len(policyName) > 128 || !matchRoleName(policyName) { 65 return trace.BadParameter("policy name is invalid") 66 } 67 return nil 68 } 69 70 // IsValidRegion ensures the region looks to be valid. 71 // It does not do a full validation, because AWS doesn't provide documentation for that. 72 // However, they usually only have the following chars: [a-z0-9\-] 73 func IsValidRegion(region string) error { 74 if matchRegion.MatchString(region) { 75 return nil 76 } 77 return trace.BadParameter("region %q is invalid", region) 78 } 79 80 // IsValidPartition checks if partition is a valid AWS partition 81 func IsValidPartition(partition string) error { 82 if slices.Contains(validPartitions, partition) { 83 return nil 84 } 85 return trace.BadParameter("partition %q is invalid", partition) 86 } 87 88 // IsValidAthenaWorkgroupName checks whether the name is a valid AWS Athena 89 // workgroup name. 90 func IsValidAthenaWorkgroupName(workgroup string) error { 91 if matchAthenaWorkgroupName(workgroup) { 92 return nil 93 } 94 return trace.BadParameter("athena workgroup name %q is invalid", workgroup) 95 } 96 97 // IsValidGlueResourceName check whether the name is valid for an AWS Glue 98 // database or table used with AWS Athena 99 func IsValidGlueResourceName(name string) error { 100 if matchGlueName(name) { 101 return nil 102 } 103 return trace.BadParameter("glue resource name %q is invalid", name) 104 } 105 106 const ( 107 arnDelimiter = ":" 108 arnPrefix = "arn:" 109 arnSections = 6 110 sectionService = 2 // arn:<partition>:<service>:... 111 sectionAccount = 4 // arn:<partition>:<service>:<region>:<accountid>:... 112 sectionResource = 5 // arn:<partition>:<service>:<region>:<accountid>:<resource> 113 iamServiceName = "iam" 114 ) 115 116 // CheckRoleARN returns whether a string is a valid IAM Role ARN. 117 // Example role ARN: arn:aws:iam::123456789012:role/some-role-name 118 func CheckRoleARN(arn string) error { 119 if !strings.HasPrefix(arn, arnPrefix) { 120 return trace.BadParameter("arn: invalid prefix: %q", arn) 121 } 122 123 sections := strings.SplitN(arn, arnDelimiter, arnSections) 124 if len(sections) != arnSections { 125 return trace.BadParameter("arn: not enough sections: %q", arn) 126 } 127 128 resourceParts := strings.SplitN(sections[sectionResource], "/", 2) 129 130 if resourceParts[0] != "role" || sections[sectionService] != iamServiceName { 131 return trace.BadParameter("%q is not an AWS IAM role ARN", arn) 132 } 133 134 if len(resourceParts) < 2 || resourceParts[1] == "" { 135 return trace.BadParameter("%q is missing AWS IAM role name", arn) 136 } 137 138 if err := IsValidAccountID(sections[sectionAccount]); err != nil { 139 return trace.BadParameter("%q invalid account ID: %v", arn, err) 140 } 141 142 return nil 143 } 144 145 var ( 146 // matchRoleName is a regex that matches against AWS IAM Role Names. 147 matchRoleName = regexp.MustCompile(`^[\w+=,.@-]+$`).MatchString 148 149 // matchRegion is a regex that defines the format of AWS regions. 150 // 151 // The regex matches the following from left to right: 152 // - starts with 2 lower case letters that represents a geo region like a 153 // country code 154 // - optional -gov, -iso, -isob for corresponding partitions 155 // - a word that should be a direction like "east", "west", etc. 156 // - a number counter 157 // 158 // Reference: 159 // https://github.com/aws/aws-sdk-go-v2/blob/main/codegen/smithy-aws-go-codegen/src/main/resources/software/amazon/smithy/aws/go/codegen/endpoints.json 160 matchRegion = regexp.MustCompile(`^[a-z]{2}(-gov|-iso|-isob)?-\w+-\d+$`) 161 162 // https://docs.aws.amazon.com/athena/latest/APIReference/API_CreateWorkGroup.html 163 matchAthenaWorkgroupName = regexp.MustCompile(`^[a-zA-Z0-9._-]{1,128}$`).MatchString 164 165 // https://docs.aws.amazon.com/athena/latest/ug/tables-databases-columns-names.html 166 // More strict than strictly necessary, but a good baseline 167 // > database, table, and column names must be 255 characters or fewer 168 // > Athena accepts mixed case in DDL and DML queries, but lower cases the names when it executes the query 169 // > avoid using mixed case for table or column names 170 // > special characters other than underscore (_) are not supported 171 matchGlueName = regexp.MustCompile(`^[a-z0-9_]{1,255}$`).MatchString 172 173 // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html 174 validPartitions = []string{"aws", "aws-cn", "aws-us-gov"} 175 )