-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding self signed certificates generation (#9)
- Loading branch information
1 parent
d1c9b89
commit 3449ad7
Showing
10 changed files
with
834 additions
and
564 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# -*- coding: utf-8 -*- | ||
# SPDX-License-Identifier: MPL-2.0 | ||
# Copyright 2020-2021 John Mille<[email protected]> | ||
|
||
"""AWS module.""" | ||
|
||
import re | ||
|
||
import boto3 | ||
from boto3 import session | ||
from botocore.exceptions import ClientError | ||
|
||
from ecs_files_composer import input | ||
from ecs_files_composer.common import LOG | ||
from ecs_files_composer.envsubst import expandvars | ||
|
||
|
||
def create_session_from_creds(tmp_creds, region=None): | ||
""" | ||
Function to easily convert the AssumeRole reply into a boto3 session | ||
:param tmp_creds: | ||
:return: | ||
:rtype boto3.session.Session | ||
""" | ||
creds = tmp_creds["Credentials"] | ||
params = { | ||
"aws_access_key_id": creds["AccessKeyId"], | ||
"aws_secret_access_key": creds["SecretAccessKey"], | ||
"aws_session_token": creds["SessionToken"], | ||
} | ||
if region: | ||
params["region_name"] = region | ||
return boto3.session.Session(**params) | ||
|
||
|
||
class AwsResourceHandler(object): | ||
""" | ||
Class to handle all AWS related credentials init. | ||
""" | ||
|
||
def __init__(self, role_arn=None, external_id=None, region=None, iam_config_object=None): | ||
""" | ||
:param str role_arn: | ||
:param str external_id: | ||
:param str region: | ||
:param ecs_files_composer.input.IamOverrideDef iam_config_object: | ||
""" | ||
self.session = session.Session() | ||
self.client_session = session.Session() | ||
if role_arn or iam_config_object: | ||
if role_arn and not iam_config_object: | ||
params = {"RoleArn": role_arn, "RoleSessionName": "EcsConfigComposer@AwsResourceHandlerInit"} | ||
if external_id: | ||
params["ExternalId"] = external_id | ||
tmp_creds = self.session.client("sts").assume_role(**params) | ||
self.client_session = create_session_from_creds(tmp_creds, region=region) | ||
elif iam_config_object: | ||
params = { | ||
"RoleArn": iam_config_object.role_arn, | ||
"RoleSessionName": f"{iam_config_object.session_name}@AwsResourceHandlerInit", | ||
} | ||
if iam_config_object.external_id: | ||
params["ExternalId"] = iam_config_object.external_id | ||
tmp_creds = self.session.client("sts").assume_role(**params) | ||
self.client_session = create_session_from_creds(tmp_creds, region=iam_config_object.region_name) | ||
|
||
|
||
class S3Fetcher(AwsResourceHandler): | ||
""" | ||
Class to handle S3 actions | ||
""" | ||
|
||
def __init__(self, role_arn=None, external_id=None, region=None, iam_config_object=None): | ||
super().__init__(role_arn, external_id, region, iam_config_object) | ||
self.client = self.client_session.client("s3") | ||
|
||
def get_content(self, s3_uri=None, s3_bucket=None, s3_key=None): | ||
""" | ||
Retrieves a file in a temp dir and returns content | ||
:param str s3_uri: | ||
:param str s3_bucket: | ||
:param str s3_key: | ||
:return: The Stream Body for the file, allowing to do various things | ||
""" | ||
bucket_re = re.compile(r"(?:s3://)(?P<bucket>[a-z0-9-.]+)/(?P<key>[\S]+$)") | ||
if s3_uri and bucket_re.match(s3_uri).groups(): | ||
s3_bucket = bucket_re.match(s3_uri).group("bucket") | ||
s3_key = bucket_re.match(s3_uri).group("key") | ||
try: | ||
file_r = self.client.get_object(Bucket=s3_bucket, Key=s3_key) | ||
file_content = file_r["Body"] | ||
return file_content | ||
except self.client.exceptions.NoSuchKey: | ||
LOG.error(f"Failed to download the file {s3_key} from bucket {s3_bucket}") | ||
raise | ||
|
||
|
||
class SsmFetcher(AwsResourceHandler): | ||
""" | ||
Class to handle SSM actions | ||
""" | ||
|
||
def __init__(self, role_arn=None, external_id=None, region=None, iam_config_object=None): | ||
super().__init__(role_arn, external_id, region, iam_config_object) | ||
self.client = self.client_session.client("ssm") | ||
|
||
def get_content(self, parameter_name): | ||
""" | ||
Import the Content of a given parameter | ||
:param parameter_name: | ||
:return: | ||
""" | ||
try: | ||
parameter = self.client.get_parameter(Name=parameter_name, WithDecryption=True) | ||
return parameter["Parameter"]["Value"] | ||
except self.client.exceptions: | ||
raise | ||
except ClientError: | ||
raise | ||
|
||
|
||
class SecretFetcher(AwsResourceHandler): | ||
""" | ||
Class to handle Secret Manager actions | ||
""" | ||
|
||
def __init__(self, role_arn=None, external_id=None, region=None, iam_config_object=None): | ||
super().__init__(role_arn, external_id, region, iam_config_object) | ||
self.client = self.client_session.client("secretsmanager") | ||
|
||
def get_content(self, secret): | ||
""" | ||
Import the Content of a given parameter | ||
:param input.SecretDef secret: | ||
:return: | ||
""" | ||
secret_id = expandvars(secret.secret_id) | ||
params = {"SecretId": secret_id} | ||
LOG.debug(f"Retrieving secretsmanager://{secret_id}") | ||
if secret.version_id: | ||
params["VersionId"] = secret.version_id | ||
if secret.version_stage: | ||
params["VersionStage"] = secret.version_stage | ||
try: | ||
parameter = self.client.get_secret_value(**params) | ||
return parameter["SecretString"] | ||
except self.client.exceptions: | ||
raise | ||
except ClientError: | ||
raise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# -*- coding: utf-8 -*- | ||
# SPDX-License-Identifier: MPL-2.0 | ||
# Copyright 2020-2021 John Mille<[email protected]> | ||
|
||
import pathlib | ||
import socket | ||
from os import path | ||
from typing import Any | ||
|
||
from OpenSSL import crypto | ||
|
||
from ecs_files_composer.files_mgmt import File | ||
from ecs_files_composer.input import X509CertDef | ||
|
||
|
||
class X509Certificate(X509CertDef, object): | ||
""" | ||
Class to wrap actions around a new X509 certificate | ||
""" | ||
|
||
def __init__(self, **data: Any): | ||
super().__init__(**data) | ||
self.key = None | ||
self.cert = None | ||
self.key_content = None | ||
self.cert_content = None | ||
self.cert_file = None | ||
self.key_file = None | ||
self.cert_file_path = None | ||
self.key_file_path = None | ||
|
||
def init_cert_paths(self): | ||
self.cert_file_path = path.abspath(f"{self.dir_path}/{self.cert_file_name}") | ||
self.key_file_path = path.abspath(f"{self.dir_path}/{self.key_file_name}") | ||
print(f"Creating {self.dir_path} folder") | ||
dir_path = pathlib.Path(path.abspath(self.dir_path)) | ||
dir_path.mkdir(parents=True, exist_ok=True) | ||
|
||
def generate_key(self): | ||
self.key = crypto.PKey() | ||
self.key.generate_key(crypto.TYPE_RSA, 4096) | ||
|
||
def set_common_name(self): | ||
if self.common_name is None: | ||
self.common_name = socket.gethostname() | ||
|
||
def generate_cert(self): | ||
if not self.common_name: | ||
self.set_common_name() | ||
self.cert = crypto.X509() | ||
self.cert.get_subject().C = self.country_name | ||
self.cert.get_subject().ST = self.state_or_province_name | ||
self.cert.get_subject().L = self.locality_name | ||
self.cert.get_subject().O = self.organization_name | ||
self.cert.get_subject().OU = self.organization_unit_name | ||
self.cert.get_subject().CN = self.common_name | ||
self.cert.get_subject().emailAddress = self.email_address | ||
self.cert.set_serial_number(0) | ||
self.cert.gmtime_adj_notBefore(0) | ||
self.cert.gmtime_adj_notAfter(int(self.validity_end_in_seconds)) | ||
self.cert.set_issuer(self.cert.get_subject()) | ||
self.cert.set_pubkey(self.key) | ||
self.cert.sign(self.key, 'sha512') | ||
|
||
def generate_cert_content(self): | ||
if not self.key: | ||
self.generate_key() | ||
if not self.cert: | ||
self.generate_cert() | ||
self.cert_content = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert).decode("utf-8") | ||
self.key_content = crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key).decode("utf-8") | ||
|
||
def set_cert_files(self): | ||
if not self.cert_content or not self.key_content: | ||
self.generate_cert_content() | ||
self.key_file = File().parse_obj( | ||
{ | ||
"content": self.key_content, | ||
"path": self.key_file_path, | ||
"mode": "0600", | ||
"owner": self.owner, | ||
"group": self.group, | ||
} | ||
) | ||
self.cert_file = File().parse_obj( | ||
{ | ||
"content": self.cert_content, | ||
"path": self.cert_file_path, | ||
"mode": "0600", | ||
"owner": self.owner, | ||
"group": self.group, | ||
} | ||
) | ||
|
||
|
||
def process_x509_certs(job): | ||
""" | ||
:param ecs_files_composer.input.Model job: | ||
:return: | ||
""" | ||
if not hasattr(job.certificates, "x509") or not job.certificates.x509: | ||
return | ||
for cert_path, cert_def in job.certificates.x509.items(): | ||
cert_obj = X509Certificate( | ||
keyFileName=cert_def["keyFileName"], certFileName=cert_def["certFileName"] | ||
).parse_obj(cert_def) | ||
cert_obj.dir_path = cert_path | ||
cert_obj.init_cert_paths() | ||
cert_obj.set_cert_files() | ||
job.certificates.x509[cert_path] = cert_obj | ||
job.files[cert_obj.cert_file.path] = cert_obj.cert_file | ||
job.files[cert_obj.key_file_path] = cert_obj.key_file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.