 236455fc45
			
		
	
	236455fc45
	
	
	
		
			
			* fix missing min_healthy_percent which was causing an error on stdout...sigh Signed-off-by: Jens Langhammer <jens@goauthentik.io> * disable version reporting (replaces deleting BootstrapVersion) Signed-off-by: Jens Langhammer <jens@goauthentik.io> * dont generate bootstrap thing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * aaand remove fix_template Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always set CI to false so errors are sent to stderr Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move aws stuff to lifecycle Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix missing package-lock Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix package Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup website structure Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
		
			
				
	
	
		
			429 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| import json
 | |
| 
 | |
| from aws_cdk import (
 | |
|     App,
 | |
|     CfnOutput,
 | |
|     CfnParameter,
 | |
|     DefaultStackSynthesizer,
 | |
|     Duration,
 | |
|     RemovalPolicy,
 | |
|     Stack,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_ec2 as ec2,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_ecs as ecs,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_efs as efs,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_elasticache as elasticache,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_elasticloadbalancingv2 as elbv2,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_rds as rds,
 | |
| )
 | |
| from aws_cdk import (
 | |
|     aws_secretsmanager as secretsmanager,
 | |
| )
 | |
| from constructs import Construct
 | |
| 
 | |
| from authentik import __version__
 | |
| 
 | |
| 
 | |
| class AuthentikStack(Stack):
 | |
|     def __init__(self, scope: Construct, id: str, **kwargs):
 | |
|         super().__init__(scope, id, **kwargs)
 | |
| 
 | |
|         ### Inputs
 | |
| 
 | |
|         db_instance_type = CfnParameter(
 | |
|             self,
 | |
|             "DBInstanceType",
 | |
|             type="String",
 | |
|             default="m5.large",
 | |
|             description="RDS PostgreSQL instance type (without the leading db.)",
 | |
|         )
 | |
|         db_version = CfnParameter(
 | |
|             self, "DBVersion", type="String", default="17.1", description="RDS PostgreSQL version"
 | |
|         )
 | |
|         db_storage = CfnParameter(
 | |
|             self,
 | |
|             "DBStorage",
 | |
|             type="Number",
 | |
|             default=10,
 | |
|             min_value=10,
 | |
|             description="RDS PostgreSQL storage size in GB",
 | |
|         )
 | |
| 
 | |
|         redis_instance_type = CfnParameter(
 | |
|             self,
 | |
|             "RedisInstanceType",
 | |
|             type="String",
 | |
|             default="cache.t4g.medium",
 | |
|             description="ElastiCache Redis instance type (with the leading cache.)",
 | |
|         )
 | |
|         redis_version = CfnParameter(
 | |
|             self,
 | |
|             "RedisVersion",
 | |
|             type="String",
 | |
|             default="7.1",
 | |
|             description="ElastiCache Redis version",
 | |
|         )
 | |
| 
 | |
|         authentik_image = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikImage",
 | |
|             type="String",
 | |
|             default="ghcr.io/goauthentik/server",
 | |
|             description="authentik Docker image",
 | |
|         )
 | |
|         authentik_version = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikVersion",
 | |
|             type="String",
 | |
|             default=__version__,
 | |
|             description="authentik Docker image tag",
 | |
|         )
 | |
| 
 | |
|         server_cpu = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikServerCPU",
 | |
|             type="Number",
 | |
|             default=512,
 | |
|             description="authentik server CPU units (1024 = 1 vCPU)",
 | |
|         )
 | |
|         server_memory = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikServerMemory",
 | |
|             type="Number",
 | |
|             default=1024,
 | |
|             description="authentik server memory in MiB",
 | |
|         )
 | |
|         server_desired_count = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikServerDesiredCount",
 | |
|             type="Number",
 | |
|             default=2,
 | |
|             min_value=1,
 | |
|             description="Desired number of authentik server tasks",
 | |
|         )
 | |
| 
 | |
|         worker_cpu = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikWorkerCPU",
 | |
|             type="Number",
 | |
|             default=512,
 | |
|             description="authentik worker CPU units (1024 = 1 vCPU)",
 | |
|         )
 | |
|         worker_memory = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikWorkerMemory",
 | |
|             type="Number",
 | |
|             default=1024,
 | |
|             description="authentik worker memory in MiB",
 | |
|         )
 | |
|         worker_desired_count = CfnParameter(
 | |
|             self,
 | |
|             "AuthentikWorkerDesiredCount",
 | |
|             type="Number",
 | |
|             default=2,
 | |
|             min_value=1,
 | |
|             description="Desired number of authentik worker tasks",
 | |
|         )
 | |
| 
 | |
|         certificate_arn = CfnParameter(
 | |
|             self,
 | |
|             "CertificateARN",
 | |
|             type="String",
 | |
|             description="ACM certificate ARN for HTTPS access",
 | |
|         )
 | |
| 
 | |
|         ### Resources
 | |
| 
 | |
|         # VPC
 | |
| 
 | |
|         vpc = ec2.Vpc(self, "AuthentikVpc", max_azs=2, nat_gateways=1)
 | |
| 
 | |
|         # Security Groups
 | |
| 
 | |
|         db_security_group = ec2.SecurityGroup(
 | |
|             self, "DatabaseSG", vpc=vpc, description="Security Group for authentik RDS PostgreSQL"
 | |
|         )
 | |
|         redis_security_group = ec2.SecurityGroup(
 | |
|             self, "RedisSG", vpc=vpc, description="Security Group for authentik ElastiCache Redis"
 | |
|         )
 | |
|         authentik_security_group = ec2.SecurityGroup(
 | |
|             self, "AuthentikSG", vpc=vpc, description="Security Group for authentik services"
 | |
|         )
 | |
|         db_security_group.add_ingress_rule(
 | |
|             peer=authentik_security_group,
 | |
|             connection=ec2.Port.tcp(5432),
 | |
|             description="Allow authentik to connect to RDS PostgreSQL",
 | |
|         )
 | |
|         redis_security_group.add_ingress_rule(
 | |
|             peer=authentik_security_group,
 | |
|             connection=ec2.Port.tcp(6379),
 | |
|             description="Allow authentik to connect to ElastiCache Redis",
 | |
|         )
 | |
| 
 | |
|         # Generated secrets
 | |
| 
 | |
|         db_password = secretsmanager.Secret(
 | |
|             self,
 | |
|             "DBPassword",
 | |
|             generate_secret_string=secretsmanager.SecretStringGenerator(
 | |
|                 secret_string_template=json.dumps({"username": "authentik"}),
 | |
|                 generate_string_key="password",
 | |
|                 password_length=64,
 | |
|                 exclude_characters='"@/\\',
 | |
|             ),
 | |
|         )
 | |
|         secret_key = secretsmanager.Secret(
 | |
|             self,
 | |
|             "AuthentikSecretKey",
 | |
|             generate_secret_string=secretsmanager.SecretStringGenerator(
 | |
|                 password_length=64, exclude_characters='"@/\\'
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|         # Database
 | |
| 
 | |
|         database = rds.DatabaseInstance(
 | |
|             self,
 | |
|             "AuthentikDB",
 | |
|             engine=rds.DatabaseInstanceEngine.postgres(
 | |
|                 version=rds.PostgresEngineVersion.of(db_version.value_as_string, ""),
 | |
|             ),
 | |
|             instance_type=ec2.InstanceType(db_instance_type.value_as_string),
 | |
|             vpc=vpc,
 | |
|             vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
 | |
|             allocated_storage=db_storage.value_as_number,
 | |
|             security_groups=[db_security_group],
 | |
|             database_name="authentik",
 | |
|             credentials=rds.Credentials.from_secret(db_password),
 | |
|             multi_az=True,
 | |
|             removal_policy=RemovalPolicy.SNAPSHOT,
 | |
|         )
 | |
| 
 | |
|         # Redis
 | |
| 
 | |
|         redis_subnet_group = elasticache.CfnSubnetGroup(
 | |
|             self,
 | |
|             "AuthentikRedisSubnetGroup",
 | |
|             subnet_ids=vpc.select_subnets(
 | |
|                 subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS
 | |
|             ).subnet_ids,
 | |
|             description="Subnet group for authentik ElastiCache Redis",
 | |
|         )
 | |
| 
 | |
|         redis = elasticache.CfnReplicationGroup(
 | |
|             self,
 | |
|             "AuthentikRedis",
 | |
|             replication_group_description="Redis cluster for authentik",
 | |
|             engine="redis",
 | |
|             engine_version=redis_version.value_as_string,
 | |
|             cache_node_type=redis_instance_type.value_as_string,
 | |
|             num_cache_clusters=2,
 | |
|             automatic_failover_enabled=True,
 | |
|             security_group_ids=[redis_security_group.security_group_id],
 | |
|             cache_subnet_group_name=redis_subnet_group.ref,
 | |
|         )
 | |
| 
 | |
|         # Storage
 | |
| 
 | |
|         media_fs = efs.FileSystem(
 | |
|             self,
 | |
|             "AuthentikMediaEFS",
 | |
|             vpc=vpc,
 | |
|             removal_policy=RemovalPolicy.RETAIN,
 | |
|             security_group=ec2.SecurityGroup(
 | |
|                 self,
 | |
|                 "AuthentikMediaEFSSecurityGroup",
 | |
|                 vpc=vpc,
 | |
|                 description="Security group for authentik media EFS",
 | |
|                 allow_all_outbound=True,
 | |
|             ),
 | |
|             encrypted=True,
 | |
|             performance_mode=efs.PerformanceMode.GENERAL_PURPOSE,
 | |
|             throughput_mode=efs.ThroughputMode.BURSTING,
 | |
|         )
 | |
|         media_fs.connections.allow_default_port_from(authentik_security_group)
 | |
| 
 | |
|         media_access_point = media_fs.add_access_point(
 | |
|             "AuthentikMediaAccessPoint",
 | |
|             path="/media",
 | |
|             create_acl=efs.Acl(owner_uid="1000", owner_gid="1000", permissions="755"),
 | |
|             posix_user=efs.PosixUser(uid="1000", gid="1000"),
 | |
|         )
 | |
| 
 | |
|         # ECS Cluster
 | |
| 
 | |
|         cluster = ecs.Cluster(self, "AuthentikCluster", vpc=vpc)
 | |
| 
 | |
|         environment = {
 | |
|             "AUTHENTIK_POSTGRESQL__HOST": database.instance_endpoint.hostname,
 | |
|             "AUTHENTIK_POSTGRESQL__USER": "authentik",
 | |
|             "AUTHENTIK_REDIS__HOST": redis.attr_primary_end_point_address,
 | |
|         }
 | |
| 
 | |
|         secrets = {
 | |
|             "AUTHENTIK_POSTGRESQL__PASSWORD": ecs.Secret.from_secrets_manager(
 | |
|                 db_password, field="password"
 | |
|             ),
 | |
|             "AUTHENTIK_SECRET_KEY": ecs.Secret.from_secrets_manager(secret_key),
 | |
|         }
 | |
| 
 | |
|         server_task = ecs.FargateTaskDefinition(
 | |
|             self,
 | |
|             "AuthentikServerTask",
 | |
|             cpu=server_cpu.value_as_number,
 | |
|             memory_limit_mib=server_memory.value_as_number,
 | |
|         )
 | |
|         server_task.add_volume(
 | |
|             name="media",
 | |
|             efs_volume_configuration=ecs.EfsVolumeConfiguration(
 | |
|                 file_system_id=media_fs.file_system_id,
 | |
|                 transit_encryption="ENABLED",
 | |
|                 authorization_config=ecs.AuthorizationConfig(
 | |
|                     access_point_id=media_access_point.access_point_id,
 | |
|                     iam="ENABLED",
 | |
|                 ),
 | |
|             ),
 | |
|         )
 | |
|         server_container = server_task.add_container(
 | |
|             "AuthentikServerContainer",
 | |
|             image=ecs.ContainerImage.from_registry(
 | |
|                 f"{authentik_image.value_as_string}:{authentik_version.value_as_string}"
 | |
|             ),
 | |
|             command=["server"],
 | |
|             environment=environment,
 | |
|             secrets=secrets,
 | |
|             logging=ecs.LogDriver.aws_logs(stream_prefix="authentik-server"),
 | |
|             enable_restart_policy=True,
 | |
|             health_check=ecs.HealthCheck(
 | |
|                 command=["CMD", "ak", "healthcheck"],
 | |
|                 interval=Duration.seconds(30),
 | |
|                 retries=3,
 | |
|                 start_period=Duration.seconds(60),
 | |
|                 timeout=Duration.seconds(30),
 | |
|             ),
 | |
|         )
 | |
|         server_container.add_port_mappings(ecs.PortMapping(container_port=9000))
 | |
|         server_container.add_mount_points(
 | |
|             ecs.MountPoint(container_path="/media", source_volume="media", read_only=False)
 | |
|         )
 | |
|         server_service = ecs.FargateService(
 | |
|             self,
 | |
|             "AuthentikServerService",
 | |
|             cluster=cluster,
 | |
|             task_definition=server_task,
 | |
|             desired_count=server_desired_count.value_as_number,
 | |
|             security_groups=[authentik_security_group],
 | |
|             vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
 | |
|             enable_execute_command=True,
 | |
|             min_healthy_percent=50,
 | |
|         )
 | |
| 
 | |
|         worker_task = ecs.FargateTaskDefinition(
 | |
|             self,
 | |
|             "AuthentikWorkerTask",
 | |
|             cpu=worker_cpu.value_as_number,
 | |
|             memory_limit_mib=worker_memory.value_as_number,
 | |
|         )
 | |
|         worker_task.add_volume(
 | |
|             name="media",
 | |
|             efs_volume_configuration=ecs.EfsVolumeConfiguration(
 | |
|                 file_system_id=media_fs.file_system_id,
 | |
|                 transit_encryption="ENABLED",
 | |
|                 authorization_config=ecs.AuthorizationConfig(
 | |
|                     access_point_id=media_access_point.access_point_id,
 | |
|                     iam="ENABLED",
 | |
|                 ),
 | |
|             ),
 | |
|         )
 | |
|         worker_container = worker_task.add_container(
 | |
|             "AuthentikWorkerContainer",
 | |
|             image=ecs.ContainerImage.from_registry(
 | |
|                 f"{authentik_image.value_as_string}:{authentik_version.value_as_string}"
 | |
|             ),
 | |
|             command=["worker"],
 | |
|             environment=environment,
 | |
|             secrets=secrets,
 | |
|             logging=ecs.LogDriver.aws_logs(stream_prefix="authentik-worker"),
 | |
|             enable_restart_policy=True,
 | |
|             health_check=ecs.HealthCheck(
 | |
|                 command=["CMD", "ak", "healthcheck"],
 | |
|                 interval=Duration.seconds(30),
 | |
|                 retries=3,
 | |
|                 start_period=Duration.seconds(60),
 | |
|                 timeout=Duration.seconds(30),
 | |
|             ),
 | |
|         )
 | |
|         worker_container.add_mount_points(
 | |
|             ecs.MountPoint(container_path="/media", source_volume="media", read_only=False)
 | |
|         )
 | |
|         worker_service = ecs.FargateService(  # noqa: F841
 | |
|             self,
 | |
|             "AuthentikWorkerService",
 | |
|             cluster=cluster,
 | |
|             task_definition=worker_task,
 | |
|             desired_count=worker_desired_count.value_as_number,
 | |
|             security_groups=[authentik_security_group],
 | |
|             vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
 | |
|             enable_execute_command=True,
 | |
|             min_healthy_percent=50,
 | |
|         )
 | |
| 
 | |
|         # Load balancer
 | |
| 
 | |
|         lb = elbv2.ApplicationLoadBalancer(
 | |
|             self,
 | |
|             "AuthentikALB",
 | |
|             vpc=vpc,
 | |
|             internet_facing=True,
 | |
|         )
 | |
|         https_redirect = lb.add_listener(  # noqa: F841
 | |
|             "AuthentikHttpListener",
 | |
|             port=80,
 | |
|             default_action=elbv2.ListenerAction.redirect(permanent=True, protocol="HTTPS"),
 | |
|         )
 | |
|         listener = lb.add_listener(
 | |
|             "AuthentikHttpsListener",
 | |
|             port=443,
 | |
|             certificates=[
 | |
|                 elbv2.ListenerCertificate(certificate_arn=certificate_arn.value_as_string)
 | |
|             ],
 | |
|         )
 | |
|         target_group = listener.add_targets(  # noqa: F841
 | |
|             "AuthentikServerTarget",
 | |
|             protocol=elbv2.ApplicationProtocol.HTTP,
 | |
|             port=9000,
 | |
|             targets=[server_service],
 | |
|             health_check=elbv2.HealthCheck(
 | |
|                 path="/-/health/live/",
 | |
|                 healthy_http_codes="200",
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|         CfnOutput(
 | |
|             self,
 | |
|             "LoadBalancerDNS",
 | |
|             value=lb.load_balancer_dns_name,
 | |
|         )
 | |
| 
 | |
| 
 | |
| app = App()
 | |
| AuthentikStack(
 | |
|     app,
 | |
|     "AuthentikStack",
 | |
|     synthesizer=DefaultStackSynthesizer(generate_bootstrap_version_rule=False),
 | |
| )
 | |
| app.synth()
 |