def _second_payload(self, server_payload): """Return the second and final SASL payload.""" if not server_payload: raise PyMongoAuthAwsError( 'MONGODB-AWS failed: server payload empty') server_payload = self.bson_decode(server_payload) server_nonce = server_payload['s'] if len(server_nonce) != 64 or not server_nonce.startswith( self._client_nonce): raise PyMongoAuthAwsError("Server returned an invalid nonce.") sts_host = server_payload['h'] payload = _aws_auth_header(self._credentials, server_nonce, sts_host) return self.binary_type()(self.bson_encode(payload))
def _get_region(sts_host): """Return the AWS region to use for the given host.""" # Drivers must also validate that the host is greater than 0 and # less than or equal to 255 bytes per RFC 1035. if not sts_host or len(sts_host) > 255: raise PyMongoAuthAwsError( "Server returned an invalid sts host: %s" % (sts_host,)) parts = sts_host.split('.') if len(parts) == 1 or sts_host == 'sts.amazonaws.com': return 'us-east-1' # Default # Check for empty labels (eg "invalid..host" or ".invalid.host"). if not all(parts): raise PyMongoAuthAwsError( "Server returned an invalid sts host: %s" % (sts_host,)) return parts[1]
def step(self, server_payload): """Step through the SASL conversation. :Parameters: - `server_payload`: The server payload (SASL challenge). Must be a bytes-like object. :Returns: The response payload for the next SASL step. :Raises: :class:`~pymongo_auth_aws.PyMongoAuthAwsError` on error. """ self._step += 1 if self._step == 1: return self._first_payload() elif self._step == 2: return self._second_payload(server_payload) else: raise PyMongoAuthAwsError('MONGODB-AWS failed: too many steps') pass
def _aws_temp_credentials(): """Construct temporary MONGODB-AWS credentials.""" access_key = os.environ.get('AWS_ACCESS_KEY_ID') secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') if access_key and secret_key: return AwsCredential( access_key, secret_key, os.environ.get('AWS_SESSION_TOKEN')) # If the environment variable # AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set then drivers MUST # assume that it was set by an AWS ECS agent and use the URI # http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI to # obtain temporary credentials. relative_uri = os.environ.get('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI') if relative_uri is not None: try: res = requests.get(_AWS_REL_URI+relative_uri, timeout=_AWS_HTTP_TIMEOUT) res_json = res.json() except (ValueError, requests.exceptions.RequestException) as exc: raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained, ' 'error: %s' % (exc,)) else: # If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is # not set drivers MUST assume we are on an EC2 instance and use the # endpoint # http://169.254.169.254/latest/meta-data/iam/security-credentials # /<role-name> # whereas role-name can be obtained from querying the URI # http://169.254.169.254/latest/meta-data/iam/security-credentials/. try: # Get token headers = {'X-aws-ec2-metadata-token-ttl-seconds': "30"} res = requests.put(_AWS_EC2_URI+'latest/api/token', headers=headers, timeout=_AWS_HTTP_TIMEOUT) token = res.content # Get role name headers = {'X-aws-ec2-metadata-token': token} res = requests.get(_AWS_EC2_URI+_AWS_EC2_PATH, headers=headers, timeout=_AWS_HTTP_TIMEOUT) role = res.text # Get temp creds res = requests.get(_AWS_EC2_URI+_AWS_EC2_PATH+role, headers=headers, timeout=_AWS_HTTP_TIMEOUT) res_json = res.json() except (ValueError, requests.exceptions.RequestException) as exc: raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained, ' 'error: %s' % (exc,)) try: temp_user = res_json['AccessKeyId'] temp_password = res_json['SecretAccessKey'] token = res_json['Token'] except KeyError: # If temporary credentials cannot be obtained then drivers MUST # fail authentication and raise an error. raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained') return AwsCredential(temp_user, temp_password, token)
def _aws_temp_credentials(): """Construct temporary MONGODB-AWS credentials.""" global _cached_credentials # Store the variable locally for safe threaded access. creds = _cached_credentials access_key = os.environ.get('AWS_ACCESS_KEY_ID') secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') if access_key and secret_key: return AwsCredential(access_key, secret_key, os.environ.get('AWS_SESSION_TOKEN')) # Check to see if we have valid credentials. if creds and creds.expiration is not None: now_utc = datetime.now(utc) exp_utc = parse_to_aware_datetime(creds.expiration) if (exp_utc - now_utc).total_seconds() >= _credential_buffer_seconds: return creds # Check if environment variables exposed by IAM Roles for Service Accounts (IRSA) are present. # See https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html for details. irsa_web_id_file = os.getenv('AWS_WEB_IDENTITY_TOKEN_FILE') irsa_role_arn = os.getenv('AWS_ROLE_ARN') if irsa_web_id_file and irsa_role_arn: try: with open(irsa_web_id_file) as f: irsa_web_id_token = f.read() role_session_name = os.getenv('AWS_ROLE_SESSION_NAME', 'pymongo-auth-aws') creds = _irsa_assume_role(irsa_role_arn, irsa_web_id_token, role_session_name) _cached_credentials = creds return creds except Exception as exc: raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained, ' 'error: %s' % (exc, )) # If the environment variable # AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set then drivers MUST # assume that it was set by an AWS ECS agent and use the URI # http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI to # obtain temporary credentials. relative_uri = os.environ.get('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI') if relative_uri is not None: try: res = requests.get(_AWS_REL_URI + relative_uri, timeout=_AWS_HTTP_TIMEOUT) res_json = res.json() except (ValueError, requests.exceptions.RequestException) as exc: raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained, ' 'error: %s' % (exc, )) else: # If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is # not set drivers MUST assume we are on an EC2 instance and use the # endpoint # http://169.254.169.254/latest/meta-data/iam/security-credentials # /<role-name> # whereas role-name can be obtained from querying the URI # http://169.254.169.254/latest/meta-data/iam/security-credentials/. try: # Get token headers = {'X-aws-ec2-metadata-token-ttl-seconds': "30"} res = requests.put(_AWS_EC2_URI + 'latest/api/token', headers=headers, timeout=_AWS_HTTP_TIMEOUT) token = res.content # Get role name headers = {'X-aws-ec2-metadata-token': token} res = requests.get(_AWS_EC2_URI + _AWS_EC2_PATH, headers=headers, timeout=_AWS_HTTP_TIMEOUT) role = res.text # Get temp creds res = requests.get(_AWS_EC2_URI + _AWS_EC2_PATH + role, headers=headers, timeout=_AWS_HTTP_TIMEOUT) res_json = res.json() except (ValueError, requests.exceptions.RequestException) as exc: raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained, ' 'error: %s' % (exc, )) # See https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html#examples for expected result format. try: temp_user = res_json['AccessKeyId'] temp_password = res_json['SecretAccessKey'] session_token = res_json['Token'] expiration = res_json['Expiration'] except KeyError: # If temporary credentials cannot be obtained then drivers MUST # fail authentication and raise an error. raise PyMongoAuthAwsError( 'temporary MONGODB-AWS credentials could not be obtained') creds = AwsCredential(temp_user, temp_password, session_token, expiration) _cached_credentials = creds return creds