Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding self signed certificates generation #9

Merged
merged 1 commit into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
460 changes: 269 additions & 191 deletions ecs-files-input.json

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions ecs_files_composer/aws_mgmt.py
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
113 changes: 113 additions & 0 deletions ecs_files_composer/certificates_mgmt.py
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
34 changes: 1 addition & 33 deletions ecs_files_composer/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,7 @@
import sys
from os import environ


def keyisset(x, y):
"""
Macro to figure if the the dictionary contains a key and that the key is not empty

:param x: The key to check presence in the dictionary
:type x: str
:param y: The dictionary to check for
:type y: dict

:returns: True/False
:rtype: bool
"""
if isinstance(y, dict) and x in y.keys() and y[x]:
return True
return False


def keypresent(x, y):
"""
Macro to figure if the the dictionary contains a key and that the key is not empty

:param x: The key to check presence in the dictionary
:type x: str
:param y: The dictionary to check for
:type y: dict

:returns: True/False
:rtype: bool
"""
if isinstance(y, dict) and x in y.keys():
return True
return False
from compose_x_common.compose_x_common import keyisset


def setup_logging():
Expand Down
Loading