class CfnVpn::Templates::Vpn

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/cfnvpn/templates/vpn.rb, line 9
def initialize
  super
end

Public Instance Methods

auto_route_populator(name, bucket) click to toggle source
# File lib/cfnvpn/templates/vpn.rb, line 231
def auto_route_populator(name, bucket)
  IAM_Role(:CfnVpnAutoRoutePopulatorRole) {
    AssumeRolePolicyDocument({
      Version: '2012-10-17',
      Statement: [{
        Effect: 'Allow',
        Principal: { Service: [ 'lambda.amazonaws.com' ] },
        Action: [ 'sts:AssumeRole' ]
      }]
    })
    Path '/cfnvpn/'
    Policies([
      {
        PolicyName: 'client-vpn',
        PolicyDocument: {
          Version: '2012-10-17',
          Statement: [{
            Effect: 'Allow',
            Action: [ 
              'ec2:AuthorizeClientVpnIngress',
              'ec2:RevokeClientVpnIngress',
              'ec2:DescribeClientVpnAuthorizationRules',
              'ec2:DescribeClientVpnEndpoints',
              'ec2:DescribeClientVpnRoutes',
              'ec2:CreateClientVpnRoute',
              'ec2:DeleteClientVpnRoute'
            ],
            Resource: '*'
          }]
        }
      },
      {
        PolicyName: 'logging',
        PolicyDocument: {
          Version: '2012-10-17',
          Statement: [{
            Effect: 'Allow',
            Action: [
              'logs:DescribeLogGroups',
              'logs:CreateLogGroup',
              'logs:CreateLogStream',
              'logs:DescribeLogStreams',
              'logs:PutLogEvents'
            ],
            Resource: '*'
          }]
        }
      }
    ])
    Tags([
      { Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator-role" },
      { Key: 'Environment', Value: 'cfnvpn' }
    ])
  }

  s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'auto_route_populator', files: ['app.py'])
  
  Lambda_Function(:CfnVpnAutoRoutePopulator) {
    Runtime 'python3.8'
    Role FnGetAtt(:CfnVpnAutoRoutePopulatorRole, :Arn)
    MemorySize '128'
    Handler 'app.handler'
    Timeout 60
    Code({
      S3Bucket: bucket,
      S3Key: s3_key
    })
    Tags([
      { Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator" },
      { Key: 'Environment', Value: 'cfnvpn' }
    ])
  }

  Logs_LogGroup(:CfnVpnAutoRoutePopulatorLogGroup) {
    LogGroupName FnSub("/aws/lambda/${CfnVpnAutoRoutePopulator}")
    RetentionInDays 30
  }

  Lambda_Permission(:CfnVpnAutoRoutePopulatorFunctionPermissions) {
    FunctionName Ref(:CfnVpnAutoRoutePopulator)
    Action 'lambda:InvokeFunction'
    Principal 'events.amazonaws.com'
  }
end
output(name, value) click to toggle source
# File lib/cfnvpn/templates/vpn.rb, line 227
def output(name, value)
  Output(name) { Value value }
end
render(name, config) click to toggle source
# File lib/cfnvpn/templates/vpn.rb, line 13
def render(name, config)
  Description "cfnvpn #{name} AWS Client-VPN"
  Parameter(:AssociateSubnets) {
    Type 'String'
    Default 'true'
    AllowedValues ['true', 'false']
    Description 'Toggle to false to disassociate all Client VPN subnet associations'
  }

  Condition(:EnableSubnetAssociation, FnEquals(Ref(:AssociateSubnets), 'true'))

  Logs_LogGroup(:ClientVpnLogGroup) {
    LogGroupName FnSub("#{name}-ClientVpn")
    RetentionInDays 30
  }

  EC2_ClientVpnEndpoint(:ClientVpnEndpoint) {
    Description FnSub("cfnvpn #{name} AWS Client-VPN")
    AuthenticationOptions([
    if config[:type] == 'federated'
      {
        FederatedAuthentication: {
          SAMLProviderArn: config[:saml_arn],
          SelfServiceSAMLProviderArn: config[:saml_arn]
        },
        Type: 'federated-authentication'
      }
    elsif config[:type] == 'active-directory'
      {
        ActiveDirectory: {
          DirectoryId: config[:directory_id]
        },
        Type: 'directory-service-authentication'
      }
    else
      {
        MutualAuthentication: {
          ClientRootCertificateChainArn: config[:client_cert_arn]
        },
        Type: 'certificate-authentication'
      }
    end
    ])
    ServerCertificateArn config[:server_cert_arn]
    ClientCidrBlock config[:cidr]
    ConnectionLogOptions({
      CloudwatchLogGroup: Ref(:ClientVpnLogGroup),
      Enabled: true
    })
    DnsServers config[:dns_servers].any? ? config[:dns_servers] : Ref('AWS::NoValue')
    TagSpecifications([{
      ResourceType: "client-vpn-endpoint",
      Tags: [
        { Key: 'Name', Value: name }
      ]
    }])
    TransportProtocol config[:protocol]
    SplitTunnel config[:split_tunnel]
  }

  network_assoc_dependson = []
  config[:subnet_ids].each_with_index do |subnet, index|
    suffix = index == 0 ? "" : "For#{subnet.resource_safe}"

    EC2_ClientVpnTargetNetworkAssociation(:"ClientVpnTargetNetworkAssociation#{suffix}") {
      Condition(:EnableSubnetAssociation)
      ClientVpnEndpointId Ref(:ClientVpnEndpoint)
      SubnetId subnet
    }

    network_assoc_dependson << "ClientVpnTargetNetworkAssociation#{suffix}"
  end

  if config[:default_groups].any?
    config[:default_groups].each do |group|
      EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{group.resource_safe}"[0..255]) {
        Condition(:EnableSubnetAssociation)
        DependsOn network_assoc_dependson if network_assoc_dependson.any?
        Description FnSub("#{name} client-vpn auth rule for subnet association")
        AccessGroupId group
        ClientVpnEndpointId Ref(:ClientVpnEndpoint)
        TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
      }
    end
  else
    EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule") {
      Condition(:EnableSubnetAssociation)
      DependsOn network_assoc_dependson if network_assoc_dependson.any?
      Description FnSub("#{name} client-vpn auth rule for subnet association")
      AuthorizeAllGroups true
      ClientVpnEndpointId Ref(:ClientVpnEndpoint)
      TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
    }
  end

  if !config[:internet_route].nil?
    EC2_ClientVpnRoute(:RouteToInternet) {
      Condition(:EnableSubnetAssociation)
      DependsOn network_assoc_dependson if network_assoc_dependson.any?
      Description "Route to the internet through subnet #{config[:internet_route]}"
      ClientVpnEndpointId Ref(:ClientVpnEndpoint)
      DestinationCidrBlock '0.0.0.0/0'
      TargetVpcSubnetId config[:internet_route]
    }
  
    EC2_ClientVpnAuthorizationRule(:RouteToInternetAuthorizationRule) {
      Condition(:EnableSubnetAssociation)
      DependsOn network_assoc_dependson if network_assoc_dependson.any?
      Description "Internet route authorization from subnet #{config[:internet_route]}"
      AuthorizeAllGroups true
      ClientVpnEndpointId Ref(:ClientVpnEndpoint)
      TargetNetworkCidr '0.0.0.0/0'
    }

    output(:InternetRoute, config[:internet_route])
  end

  dns_routes = config[:routes].select {|route| route.has_key?(:dns)}
  cidr_routes = config[:routes].select {|route| route.has_key?(:cidr)}

  if dns_routes.any?
    auto_route_populator(name, config[:bucket])

    dns_routes.each do |route|
      input = { 
        Record: route[:dns],
        ClientVpnEndpointId: "${ClientVpnEndpoint}",
        TargetSubnet: route[:subnet],
        Description: route[:desc]
      }
      
      if route[:groups].any?
        input[:Groups] = route[:groups]
      end

      Events_Rule(:"CfnVpnAutoRoutePopulatorEvent#{route[:dns].resource_safe}"[0..255]) {
        State 'ENABLED'
        Description "cfnvpn auto route populator schedule for #{route[:dns]}"
        ScheduleExpression "rate(5 minutes)"
        Targets([
          { 
            Arn: FnGetAtt(:CfnVpnAutoRoutePopulator, :Arn),
            Id: "cfnvpnautoroutepopulator#{route[:dns].event_id_safe}",
            Input: FnSub(input.to_json)
          }
        ])
      }
    end
  end

  if cidr_routes.any?
    cidr_routes.each do |route|
      EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRoute") {
        Description "cfnvpn static route for #{route[:cidr]}. #{route[:desc]}".strip
        ClientVpnEndpointId Ref(:ClientVpnEndpoint)
        DestinationCidrBlock route[:cidr]
        TargetVpcSubnetId route[:subnet]
      }

      if route[:groups].any?
        route[:groups].each do |group|
          EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AuthorizationRule#{group.resource_safe}"[0..255]) {
            Description "cfnvpn static authorization rule for group #{group} to route #{route[:cidr]}. #{route[:desc]}".strip
            AccessGroupId group
            ClientVpnEndpointId Ref(:ClientVpnEndpoint)
            TargetNetworkCidr route[:cidr]
          }
        end
      else
        EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AllowAllAuthorizationRule") {
          Description "cfnvpn static allow all authorization rule to route #{route[:cidr]}. #{route[:desc]}".strip
          AuthorizeAllGroups true
          ClientVpnEndpointId Ref(:ClientVpnEndpoint)
          TargetNetworkCidr route[:cidr]
        }
      end
    end
  end
  
  SSM_Parameter(:CfnVpnConfig) {
    Description "#{name} cfnvpn config"
    Name "/cfnvpn/config/#{name}"
    Tier 'Standard'
    Type 'String'
    Value config.to_json
    Tags({
      Name: "#{name}-cfnvpn-config",
      Environment: 'cfnvpn'
    })
  }

  if config[:start] || config[:stop]
    scheduler(name, config[:start], config[:stop], config[:bucket])
    output(:Start, config[:start]) if config[:start]
    output(:Stop, config[:stop]) if config[:stop]
  end

  output(:ServerCertArn, config[:server_cert_arn])
  output(:Cidr, config[:cidr])
  output(:DnsServers, config.fetch(:dns_servers, []).join(','))
  output(:SubnetIds, config[:subnet_ids].join(','))
  output(:SplitTunnel, config[:split_tunnel])
  output(:Protocol, config[:protocol])
  output(:Type, config[:type])

  if config[:type] == 'federated'
    output(:SamlArn, config[:saml_arn])
  elsif config[:type] == 'active-directory'
    output(:DirectoryId, config[:directory_id])
  else
    output(:ClientCertArn, config[:client_cert_arn])
  end
end
scheduler(name, start, stop, bucket) click to toggle source
# File lib/cfnvpn/templates/vpn.rb, line 316
def scheduler(name, start, stop, bucket)
  IAM_Role(:ClientVpnSchedulerRole) {
    AssumeRolePolicyDocument({
      Version: '2012-10-17',
      Statement: [{
        Effect: 'Allow',
        Principal: { Service: [ 'lambda.amazonaws.com' ] },
        Action: [ 'sts:AssumeRole' ]
      }]
    })
    Path '/cfnvpn/'
    Policies([
      {
        PolicyName: 'cloudformation',
        PolicyDocument: {
          Version: '2012-10-17',
          Statement: [{
            Effect: 'Allow',
            Action: [
              'cloudformation:UpdateStack'
            ],
            Resource: FnSub("arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/#{name}-cfnvpn/*")
          }]
        }
      },
      {
        PolicyName: 'client-vpn',
        PolicyDocument: {
          Version: '2012-10-17',
          Statement: [{
            Effect: 'Allow',
            Action: [ 
              'ec2:AssociateClientVpnTargetNetwork',
              'ec2:DisassociateClientVpnTargetNetwork',
              'ec2:DescribeClientVpnTargetNetworks',
              'ec2:AuthorizeClientVpnIngress',
              'ec2:RevokeClientVpnIngress',
              'ec2:DescribeClientVpnAuthorizationRules',
              'ec2:DescribeClientVpnEndpoints',
              'ec2:DescribeClientVpnConnections',
              'ec2:TerminateClientVpnConnections'
            ],
            Resource: '*'
          }]
        }
      },
      {
        PolicyName: 'logging',
        PolicyDocument: {
          Version: '2012-10-17',
          Statement: [{
            Effect: 'Allow',
            Action: [
              'logs:DescribeLogGroups',
              'logs:CreateLogGroup',
              'logs:CreateLogStream',
              'logs:DescribeLogStreams',
              'logs:PutLogEvents'
            ],
            Resource: '*'
          }]
        }
      }
    ])
    Tags([
      { Key: 'Name', Value: "#{name}-cfnvpn-scheduler-role" },
      { Key: 'Environment', Value: 'cfnvpn' }
    ])
  }

  s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'scheduler', files: ['app.py'])

  Lambda_Function(:ClientVpnSchedulerFunction) {
    Runtime 'python3.8'
    Role FnGetAtt(:ClientVpnSchedulerRole, :Arn)
    MemorySize '128'
    Handler 'app.handler'
    Timeout 60
    Code({
      S3Bucket: bucket,
      S3Key: s3_key
    })
    Tags([
      { Key: 'Name', Value: "#{name}-cfnvpn-scheduler-function" },
      { Key: 'Environment', Value: 'cfnvpn' }
    ])
  }

  Logs_LogGroup(:ClientVpnSchedulerLogGroup) {
    LogGroupName FnSub("/aws/lambda/${ClientVpnSchedulerFunction}")
    RetentionInDays 30
  }

  Lambda_Permission(:ClientVpnSchedulerFunctionPermissions) {
    FunctionName Ref(:ClientVpnSchedulerFunction)
    Action 'lambda:InvokeFunction'
    Principal 'events.amazonaws.com'
  }

  if start
    Events_Rule(:ClientVpnSchedulerStart) {
      State 'ENABLED'
      Description "cfnvpn start schedule"
      ScheduleExpression "cron(#{start})"
      Targets([
        { 
          Arn: FnGetAtt(:ClientVpnSchedulerFunction, :Arn),
          Id: 'cfnvpnschedulerstart',
          Input: FnSub({ StackName: "#{name}-cfnvpn", AssociateSubnets: 'true', ClientVpnEndpointId: "${ClientVpnEndpoint}" }.to_json)
        }
      ])
    }
  end

  if stop
    Events_Rule(:ClientVpnSchedulerStop) {
      State 'ENABLED'
      Description "cfnvpn stop schedule"
      ScheduleExpression "cron(#{stop})"
      Targets([
        { 
          Arn: FnGetAtt(:ClientVpnSchedulerFunction, :Arn),
          Id: 'cfnvpnschedulerstop',
          Input: FnSub({ StackName: "#{name}-cfnvpn", AssociateSubnets: 'false', ClientVpnEndpointId: "${ClientVpnEndpoint}" }.to_json)
        }
      ])
    }
  end

end