diff --git a/hooks/addon b/hooks/addon index 2ca61fc..674b0c8 100755 --- a/hooks/addon +++ b/hooks/addon @@ -339,7 +339,7 @@ EOF echo -n "$(describe "Creating mount #C{${concourse_mount} (kv v$kv_version)...")" if [[ -n "$kv_version" ]] ; then desc="endpoint used for interpolating concourse pipeline secrets" - if ! safe vault secrets enable kv -path "${concourse_mount}" -version "${kv_version}" -description "${desc}" ; then + if ! safe vault secrets enable -path "${concourse_mount}" -version "${kv_version}" -description "${desc}" kv ; then bail "#R{[error]}" "Failed to create mount ${concourse_mount} -- please resolve and try again" "" fi fi diff --git a/hooks/addon-setup-approle~ar.pm b/hooks/addon-setup-approle~ar.pm index 58a211b..526e121 100644 --- a/hooks/addon-setup-approle~ar.pm +++ b/hooks/addon-setup-approle~ar.pm @@ -1,4 +1,3 @@ -#!/usr/bin/env perl # vim: set ts=2 sw=2 sts=2 foldmethod=marker package Genesis::Hook::Addon::Concourse::SetupApprole v2.7.0; @@ -43,6 +42,7 @@ sub perform { # Check if AppRole is enabled info("Ensuring Vault AppRole is enabled..."); + use Pry; pry; my $result = $self->vault->query("auth enable approle 2>&1 || true"); my @roles = (); diff --git a/hooks/blueprint.pm b/hooks/blueprint.pm index 7d336f0..ad4c762 100644 --- a/hooks/blueprint.pm +++ b/hooks/blueprint.pm @@ -1,28 +1,31 @@ -#!/usr/bin/env perl -# vim: set ts=2 sw=2 sts=2 et: package Genesis::Hook::Blueprint::Concourse v2.7.0; -use strict; +use v5.20; # Genesis min perl version is 5.20 use warnings; -use v5.20; # Only needed for development BEGIN {push @INC, $ENV{GENESIS_LIB} ? $ENV{GENESIS_LIB} : $ENV{HOME}.'/.genesis/lib'} + use parent qw(Genesis::Hook::Blueprint); -use Genesis qw/bail/; +use Genesis qw/bail in_array/; +# init - Initialize the hook {{{ sub init { my $class = shift; my $obj = $class->SUPER::init(@_); - $obj->{files} = []; $obj->check_minimum_genesis_version('3.1.0-rc.20'); return $obj; } +# }}} + +# perform - Main hook execution {{{ sub perform { my ($self) = @_; + my ($strategy,@opsfiles) = $self->validate_strategy_and_features(); + # Base files that are always included $self->add_files( "manifests/concourse/base.yml", @@ -32,204 +35,257 @@ sub perform { ); # Track operations files separately for OCFP feature - my @opsfiles = (); - - # Check for custom ops files in the deployment - for my $feature ($self->features) { - if ($feature =~ /^(azure|shield|workers|full|small-footprint|no-tls|provided-cert|self-signed-cert|github-oauth|github-enterprise-oauth|cf-oauth|vault|vault-approle|shout|prometheus|no-haproxy|dynamic-web-ip|external-db|external-db-ca|ocfp|okta|aws|stackit|\+internal-db|\+locker|\+vault-token-default|\+vault-approle-default)$/) { - # These are standard features handled below - } elsif (-f $self->env->path("ops/$feature.yml")) { - if ($self->want_feature('ocfp')) { - push @opsfiles, $self->env->path("ops/$feature.yml"); - } else { - $self->add_files($self->env->path("ops/$feature.yml")); - } - } else { - bail("The #c{%s} feature is invalid. See MANUAL.md for list of valid features.", $feature); - } - } - - # Handle CPI-specific features - if ($self->bosh_cpi() eq "azure") { - $self->add_files("manifests/addons/azure.yml"); - } - - # Make sure only one of the major feature flags is set - my $maj_feat = 0; - $maj_feat++ if $self->want_feature("ocfp"); - $maj_feat++ if $self->want_feature("full"); - $maj_feat++ if $self->want_feature("workers"); - $maj_feat++ if $self->want_feature("small-footprint"); - - if ($maj_feat != 1) { - bail("Can only have one of 'ocfp', 'full', workers', or 'small-footprint' as a feature."); - } - - if ($self->want_feature("ocfp")) { - # Enforce the 'full' feature, using OCFP vars - $self->add_files( - "manifests/concourse/full.yml", - "manifests/releases/postgres.yml", - "manifests/releases/locker.yml", - "manifests/releases/haproxy.yml", - "manifests/releases/bpm.yml" - ); - - $self->_handle_tls_features(); - $self->_handle_okta_feature(); - - # OCFP enforces the 'no-haproxy' feature - $self->add_files("manifests/addons/no-haproxy.yml"); - - # OCFP vars overrides - $self->add_files("ocfp/full-concourse-vars-override.yml"); - - # 'vault' feature, using OCFP vars - $self->add_files( - "manifests/addons/vault.yml", - "manifests/addons/vault-approle.yml", - "ocfp/vault-vars-override.yml" - ); - - # 'external-db' & 'external-db-ca' features, using OCFP vars - $self->add_files( - "manifests/addons/external-db.yml", - "manifests/addons/external-db-ca.yml", - "ocfp/external-db-vars-override.yml" - ); - - $self->add_files("ocfp/ocfp.yml"); - - if ($self->want_feature("aws")) { - $self->add_files("ocfp/iaas/aws.yml"); - } elsif ($self->want_feature("stackit")) { - $self->add_files("ocfp/iaas/stackit.yml"); - } elsif ($self->want_feature("azure") || $self->want_feature("gcp") || $self->want_feature("vsphere")) { - bail("#R{[ERROR]} The #c{azure}, #c{gcp} or #c{vsphere} features are not supported."); - } - } elsif ($self->want_feature("full") || $self->want_feature("small-footprint")) { - if ($self->want_feature("full")) { - $self->add_files( - "manifests/concourse/full.yml", - "manifests/releases/postgres.yml", - "manifests/releases/locker.yml", - "manifests/releases/haproxy.yml", - "manifests/releases/bpm.yml" - ); - } - - if ($self->want_feature("small-footprint")) { - $self->add_files( - "manifests/concourse/small-footprint.yml", - "manifests/releases/postgres.yml", - "manifests/releases/locker.yml", - "manifests/releases/bpm.yml" - ); - } - - # Handle OAuth options - for my $oauth ("github-oauth", "cf-oauth") { - if ($self->want_feature($oauth)) { - $self->add_files("manifests/oauth/$oauth.yml"); - } - } - - if ($self->want_feature("github-enterprise-oauth")) { - # github enterprise oauth just adds the host param to github oauth - if (!$self->want_feature("github-oauth")) { - $self->add_files("manifests/oauth/github-oauth.yml"); - } - $self->add_files("manifests/oauth/github-enterprise-oauth.yml"); - } - - $self->_handle_tls_features(); - $self->_handle_okta_feature(); + my $strategy_method = "build_${strategy}_blueprint" =~ s/-/_/gr; + if ($self->can($strategy_method)) { + $self->$strategy_method(); + } else { + bail("Invalid strategy method '%s' for concourse blueprint", $strategy_method); + } + + # Add any ops files if they were specified + if (@opsfiles) { + $self->add_files(@opsfiles); + } + + return $self->done(); +} - if ($self->want_feature("external-db")) { - $self->add_files("manifests/addons/external-db.yml"); +# }}} +# build_ocfp_blueprint - Build OCFP blueprint {{{ +sub build_ocfp_blueprint { + my ($self) = @_; + + # Enforce the 'full' feature, using OCFP vars + $self->add_files( + "manifests/concourse/full.yml", + "manifests/releases/postgres.yml", + "manifests/releases/locker.yml", + "manifests/releases/haproxy.yml", + "manifests/releases/bpm.yml" + ); + + # FIXME: Warn (maybe error?) if other unsupported features are requested + # that OCFP does not support. + + $self->_handle_tls_features(); + $self->_handle_okta_feature(); + + # OCFP enforces the 'no-haproxy' feature + $self->add_files("manifests/addons/no-haproxy.yml"); + + # OCFP vars overrides + $self->add_files("ocfp/full-concourse-vars-override.yml"); + + # 'vault' feature, using OCFP vars + $self->add_files( + "manifests/addons/vault.yml", + "manifests/addons/vault-approle.yml", + "ocfp/vault-vars-override.yml" + ); + + # 'external-db' & 'external-db-ca' features, using OCFP vars + $self->add_files( + "manifests/addons/external-db.yml", + "manifests/addons/external-db-ca.yml", + "ocfp/external-db-vars-override.yml" + ) unless $self->want_feature("internal-db"); + + $self->add_files("ocfp/ocfp.yml"); + + # Handle IaaS-specific OCFP files + if ($self->supports_iaas()) { + $self->kit_bug( + "This concourse kit does not seem to support OCFP on the '%s' IaaS, ". + "even though the kit.yml metadata says it does.", + $self->iaas + ) unless -f $self->kit->path("ocfp/iaas/".$self->iaas.".yml"); + $self->add_files("ocfp/iaas/".$self->iaas.".yml"); + } +} - if ($self->want_feature("external-db-ca")) { - $self->add_files("manifests/addons/external-db-ca.yml"); - } +# }}} +# build_full_blueprint - Build full blueprint {{{ +sub build_full_blueprint { + my ($self) = @_; + + $self->add_files( + "manifests/concourse/full.yml", + "manifests/releases/postgres.yml", + "manifests/releases/locker.yml", + "manifests/releases/haproxy.yml", + "manifests/releases/bpm.yml" + ); + + $self->_handle_tls_features(); + + # Handle OAuth options + $self->_handle_oauth_features(); + $self->_handle_okta_feature(); + + # Database + if ($self->want_feature("external-db")) { + $self->add_files("manifests/addons/external-db.yml"); + $self->add_files("manifests/addons/external-db-ca.yml") + if $self->want_feature("external-db-ca"); + } + + $self->add_files("manifests/addons/vault.yml") + if ($self->want_feature("vault")); + + if ($self->want_feature("vault-approle")) { + bail("Cannot use 'vault-approle' feature without 'vault' feature") + if (!$self->want_feature("vault")); + $self->add_files("manifests/addons/vault-approle.yml"); + } + + $self->add_files( + "manifests/addons/shout.yml", + "manifests/releases/shout.yml" + ) if ($self->want_feature("shout")); + + $self->add_files("manifests/addons/prometheus.yml") + if ($self->want_feature("prometheus")); + + if ($self->want_feature("no-haproxy")) { + # TODO: Is removing haproxy.yml the same as adding no-haproxy.yml? + $self->add_files("manifests/addons/no-haproxy.yml"); + $self->add_files("manifests/addons/dynamic-web.yml") + if ($self->want_feature("dynamic-web-ip")); + } + + my $max_builds = $self->env->lookup("params.max_builds_to_retain", "0"); + if ($max_builds =~ /^\d+$/ && $max_builds > 0) { + $self->add_files("manifests/addons/maximum-builds-retention.yml"); + } +} - if ($self->want_feature("small-footprint")) { - $self->add_files("manifests/addons/external-db-small.yml"); - } - } +# }}} - if ($self->want_feature("vault")) { - $self->add_files("manifests/addons/vault.yml"); - } +# build_small_footprint_blueprint - Build small footprint blueprint {{{ +sub build_small_footprint_blueprint { + my ($self) = @_; - if ($self->want_feature("vault-approle")) { - if (!$self->want_feature("vault")) { - bail("Cannot use 'vault-approle' feature without 'vault' feature"); - } - $self->add_files("manifests/addons/vault-approle.yml"); - } + # This is based on the 'full' blueprint, but with some features altered or + # removed. - if ($self->want_feature("shout")) { - $self->add_files( - "manifests/addons/shout.yml", - "manifests/releases/shout.yml" - ); - } + $self->build_full_blueprint(); - if ($self->want_feature("prometheus")) { - if ($self->want_feature("small-footprint")) { - $self->add_files("manifests/addons/prometheus-small-footprint.yml"); - } else { - $self->add_files("manifests/addons/prometheus.yml"); - } - } + $self->remove_files( + "manifests/releases/haproxy.yml", + "manifests/addons/no-haproxy.yml" + ); - if ($self->want_feature("no-haproxy") && !$self->want_feature("small-footprint")) { - $self->add_files("manifests/addons/no-haproxy.yml"); + $self->add_files("manifests/addons/external-db-small.yml") + if ($self->want_feature("external-db")); - if ($self->want_feature("dynamic-web-ip")) { - $self->add_files("manifests/addons/dynamic-web.yml"); - } - } + $self->exchange_files( + "manifests/concourse/full.yml" => "manifests/concourse/small-footprint.yml", + "manifests/addons/prometheus.yml" => "manifests/addons/prometheus-small-footprint.yml" + ); +} - my $max_builds = $self->env->lookup("params.max_builds_to_retain", "0"); - if ($max_builds =~ /^\d+$/ && $max_builds > 0) { - $self->add_files("manifests/addons/maximum-builds-retention.yml"); - } - } elsif ($self->want_feature("workers")) { - $self->add_files("manifests/concourse/workers.yml"); - - # Show warnings for incompatible features - for my $feature (qw(provided-cert self-signed-cert github-oauth github-enterprise-oauth cf-oauth vault vault-approle)) { - if ($self->want_feature($feature)) { - $self->env->notify("#Y{[WARNING]} $feature feature has no effect on worker-only deployment, and will be ignored"); - } - } - } else { - bail("Concourse needs to be configured as 'full' or 'workers'. If upgrading,". - "\nplease add 'full' to your environment's list of features"); - } +# }}} +# build_workers_blueprint - Build workers-only blueprint {{{ +sub build_workers_blueprint { + my ($self) = @_; + $self->add_files("manifests/concourse/workers.yml"); +} - # Show warnings for deprecated features - if ($self->want_feature("shield")) { - $self->env->notify("#Y{[WARNING]} The 'shield' feature is no longer supported. Instead, please add the". - "\nshield agent to your runtime configuration."); - } +# }}} + +# validate_strategy_and_features - Validate strategy and feature combinations {{{ +sub validate_strategy_and_features { + my ($self) = @_; + + # Defunct features that are no longer supported + my @defunct_features = qw( + azure aws gcp vsphere + shield + ); + my @defunct = grep { + in_array($_, @defunct_features) + } $self->features; + + bail( + "Concourse blueprint cannot be used with the following defunct features: %s%s", + join(", ", @defunct), + "\n\nIaaS features are automatically detected" . ( + in_array('shield', @defunct) ? + ", and the 'shield' feature is no longer needed." : '' + ) + ) if @defunct; + + # Strategy features + + my @strategy_features = qw( + ocfp workers full small-footprint + ); + + my ($strategy, @extra_strategies) = grep { + in_array($_, @strategy_features) + } $self->features; + + bail( + "Concourse needs to be configured with one of the following strategies: ". + "'ocfp', 'workers', 'full', or 'small-footprint'. If upgrading, please ". + "add 'full' to your environment's list of features to keep previous ". + "functionality." + ) unless $strategy; + bail( + "Concourse cannot be configured with more than one of the following ". + "strategies: 'ocfp', 'workers', 'full', or 'small-footprint'. This ". + "environment currently specifies: %s", + join(", ", @extra_strategies) + ) if @extra_strategies; + + my %valid_features = ( + ocfp => [ + qw( + internal-db self-signed-cert provided-cert no-tls + okta + ) + ], + workers => [ + ], + default => [ + qw( + provided-cert self-signed-cert no-tls + vault vault-approle shout + no-haproxy dynamic-web-ip + external-db external-db-ca + okta github-oauth github-enterprise-oauth cf-oauth + prometheus + ) + ], + ); - if ($self->want_feature("azure")) { - $self->env->notify("#Y{[WARNING]} The 'azure' feature is no longer necessary - azure CPI will be detected". - "\nautomatically at deployment time."); - } + # Check for custom ops files in the deployment + my @opsfiles = (); + my @bad_features = (); + my @good_features = exists($valid_features{$strategy}) + ? @{$valid_features{$strategy}} + : @{$valid_features{default}}; + for my $feature ($self->features) { + next if (in_array($feature, @good_features, @strategy_features)); - # Add any ops files last if using OCFP - if (@opsfiles) { - for my $ops (@opsfiles) { - $self->add_files($ops); + if (-f $self->env->path("ops/$feature.yml")) { + push @opsfiles, $self->env->path("ops/$feature.yml"); + } else { + push @bad_features, $feature; } } + bail( + "Unrecognized features found in this %s environment: %s", + $strategy, + join(", ", @bad_features), + ) if @bad_features; - return $self->done(1); + return ($strategy, @opsfiles); } +# }}} + +# _handle_tls_features - Handle TLS-related feature processing {{{ sub _handle_tls_features { my ($self) = @_; @@ -248,6 +304,9 @@ sub _handle_tls_features { } } +# }}} + +# _handle_okta_feature - Handle Okta authentication feature processing {{{ sub _handle_okta_feature { my ($self) = @_; @@ -256,5 +315,23 @@ sub _handle_okta_feature { } } -1; +# }}} + +# _handle_oauth_features - Handle OAuth-related feature processing {{{ +sub _handle_oauth_features { + my ($self) = @_; + + my @oath_features = grep {$_ =~ /-oauth$/} $self->features; + for my $oauth (@oath_features) { + my ($base, $extended) = $oauth =~ /^([^-]*)(?:-(.*))?-oauth$/; + my @files = ("manifests/oauth/$base-oauth.yml"); + push @files, "manifests/oauth/$oauth.yml" if $extended; + $self->add_files(grep {-f $self->kit->path($_)} @files); + } +} + +# }}} + +1; # End of module +# vim: set ts=2 sw=2 sts=2 noet fdm=marker foldlevel=1: diff --git a/hooks/cloud-config.pm b/hooks/cloud-config.pm index 8de328f..aa1f075 100644 --- a/hooks/cloud-config.pm +++ b/hooks/cloud-config.pm @@ -23,6 +23,13 @@ sub init { return $obj; } +sub stackit_subnet_reference { + my ($self, $property) = @_; + # Custom method to handle stackit's 1:1 network:subnet relationship + # This extracts subnet information directly instead of using network references + return $self->subnet_reference($property); +} + sub perform { my ($self) = @_; return 1 if $self->completed; @@ -37,7 +44,7 @@ sub perform { strategy => $is_ocfp ? 'ocfp' : 'manual', dynamic_subnets => { allocation => { - size => 16, + size => 12, #was 16 statics => 5, }, cloud_properties_for_iaas => { diff --git a/hooks/new.pm b/hooks/new.pm index f83248d..b0eeb18 100644 --- a/hooks/new.pm +++ b/hooks/new.pm @@ -1,52 +1,471 @@ #!/usr/bin/env perl -# vim: set ts=2 sw=2 sts=2 foldmethod=marker -package Genesis::Hook::PostDeploy::Concourse v2.7.0; +# # vim: set ts=2 sw=2 sts=2 et: +package Genesis::Hook::New::Concourse v2.7.0; use strict; use warnings; -use v5.20; # Genesis min perl version is 5.20 -use Genesis qw/info/; -use parent qw(Genesis::Hook::PostDeploy); +use v5.20; # Genesis supports min perl v5.20. + +BEGIN {push @INC, $ENV{GENESIS_LIB} ? $ENV{GENESIS_LIB} : $ENV{HOME}.'/.genesis/lib'} +use parent qw(Genesis::Hook); + +use Genesis; +use Genesis::UI qw(prompt_for_boolean); sub init { - my ($class, %ops) = @_; - my $self = $class->SUPER::init(%ops); - $self->check_minimum_genesis_version('3.1.0-rc.20'); - return $self; + my $class = shift; + my $obj = $class->SUPER::init(@_); + $obj->{features} = []; + $obj->check_minimum_genesis_version('2.7.6'); + return $obj; } sub perform { my ($self) = @_; + my $env = $self->env; + my $dir = $ENV{GENESIS_ROOT}; + my $name = $ENV{GENESIS_ENVIRONMENT}; + my $ymlfile = "$dir/$name.yml"; + my @features = (); + my $params = ""; + + info("". + "\n#G{Concourse CI/CD Genesis Kit}". + "\n#G{---------------------------}". + "\n". + "\nCreating environment #C{$name} in #C{$dir}"); + + # Get kit type + my $kit_type; + prompt_for('kit_type', 'select', + "Is this a full Concourse deployment, or a worker deployment for an existing Concourse?", + '-o "[full] Full Concourse"', + '-o "[small-footprint] Small Footprint Concourse"', + '-o "[workers] Satellite Concourse"', + \$kit_type); + + push @features, $kit_type; + + if ($kit_type eq "full" || $kit_type eq "small-footprint") { + $self->_configure_full_concourse(\@features, \$params); + } elsif ($kit_type eq "workers") { + $self->_configure_worker_concourse(\@features, \$params); + } + + # Create the environment file + $self->_write_environment_file($ymlfile, \@features, $params); + + # Offer environment editor + run({ interactive => 1 }, 'offer_environment_editor'); + + return $self->done(1); +} + +sub _configure_full_concourse { + my ($self, $features_ref, $params_ref) = @_; + + # Configure authentication + my $auth_backend_feature; + prompt_for('auth_backend_feature', 'select', + "What authentication backend do you wish to use with Concourse?", + '-o "[github-oauth] GitHub OAuth Integration"', + '-o "[github-enterprise-oauth] GitHub Enterprise OAuth Integration"', + '-o "[cf-oauth] UAA OAuth Integration"', + '-o "[] HTTP Basic Auth"', + \$auth_backend_feature); + + if ($auth_backend_feature) { + push @$features_ref, $auth_backend_feature; + } + + if ($auth_backend_feature) { + $self->_configure_oauth($auth_backend_feature, $params_ref); + } + + # Configure Vault integration + $self->_configure_vault($features_ref, $params_ref); + + # Configure external database + $self->_configure_external_db($features_ref, $params_ref); + + # Configure TLS + $self->_configure_tls($features_ref); + + # Configure external domain + $self->_configure_external_domain($params_ref); +} + +sub _configure_worker_concourse { + my ($self, $features_ref, $params_ref) = @_; + + info("". + "\nA worker-only Concourse deployment requires an existing full host Concourse". + "\ndeployment for the workers to connect to."); + + my $tsa_host_env; + prompt_for('tsa_host_env', 'line', + "Please specify environment name of the Concourse host deployment", + \$tsa_host_env); - # Call any parent methods that need to be executed - $self->SUPER::perform() if $self->can('SUPER::perform'); - - # Only show deployment info if the deployment was successful - if ($self->deploy_successful) { - my $mode = $self->env->want_feature('workers') ? "Satellite (workers only)" : "Full"; - my $call_env = $self->env->get_call_path_with_env(); - - info( - "\n#M{$ENV{GENESIS_ENVIRONMENT}} $mode Concourse deployed!\n". - "\nFor details about the deployment, run\n". - "\t#G{$ENV{GENESIS_CALL} info $ENV{GENESIS_ENVIRONMENT}}\n". - "\nTo download the '#C{fly}' CLI for the first time, run\n". - "\t#G{$call_env do -- download-fly /location/in/your/path}\n". - "\nTo update your current version of the '#C{fly}' CLI, run\n". - "\t#G{$call_env do -- download-fly --sync}\n". - "\nTo target & log into this Concourse with fly, run\n". - "\t#G{$call_env do -- login}\n". - "\nTo run a fly command against this Concourse, run (without -t )\n". - "\t#G{$call_env do -- fly [options and arguments]}\n". - "\nAs an extra efficiency, if you run the '#C{fly}' addon above, it will\n". - "\tautomatically create the target if one doesn't exist, and log you in if\n". - "\tyou're not already logged in, before running your desired command.\n". - "\nFinally, to visit the Concourse Web Portal, run:\n". - "\t#G{$call_env do -- open}\n" + $$params_ref .= " tsa_host_env: $tsa_host_env\n"; + + # Check if the host environment exists in vault + my $exodus_path = $self->env->exodus_mount . $tsa_host_env . "/concourse"; + my $exists = $self->vault->exists($exodus_path); + + if (!$exists) { + $self->env->notify( + error => "No deployment details found for $tsa_host_env Concourse deployment.". + "\nPlease ensure that it has been deployed first using Genesis v2.6 or greater and this". + "\nversion of the Concourse Genesis Kit" ); + return 0; } +} + +sub _configure_oauth { + my ($self, $auth_backend_feature, $params_ref) = @_; + my $vault_prefix = $ENV{GENESIS_SECRETS_BASE}; + + if ($auth_backend_feature eq "github-oauth" || $auth_backend_feature eq "github-enterprise-oauth") { + info("". + "\nThe GitHub OAuth Client ID and Client Secret are needed to authenticate Concourse". + "\nto GitHub, so that Concourse can then authorize users after they log into GitHub.". + "\nSee https://developer.github.com/v3/oauth/ for more info."); + + my ($client_id, $client_secret); + prompt_for('client_id', 'line', "GitHub OAuth Client ID", '-i', \$client_id); + prompt_for('client_secret', 'line', "GitHub OAuth Client Secret", '-i', \$client_secret); + + $self->vault->set("${vault_prefix}oauth", "provider_key", $client_id); + $self->vault->set("${vault_prefix}oauth", "provider_secret", $client_secret); + + info("". + "\nConcourse authorizes access based off of GitHub Organizations"); + + my $authz_allowed_orgs; + prompt_for('authz_allowed_orgs', 'line', + "Which GitHub organization do you want to grant access to Concourse?", + \$authz_allowed_orgs); + + $$params_ref .= " authz_allowed_orgs: $authz_allowed_orgs\n"; + + if ($auth_backend_feature eq "github-enterprise-oauth") { + info("". + "\nWhat is the GitHub Enterprise hostname? example: github.example.com"); + + my $github_host; + prompt_for('github_host', 'line', "GitHub Enterprise Hostname:", '-i', \$github_host); + + $$params_ref .= " github_host: $github_host\n"; + } + } elsif ($auth_backend_feature eq "cf-oauth") { + info("". + "\nThe UAA client id and secret is needed to authenticate Concourse to the UAA,". + "\nso that Concourse can then authorize users after they log into the UAA."); + + my ($client_id, $client_secret); + prompt_for('client_id', 'line', "UAA Client ID:", '-i', \$client_id); + prompt_for('client_secret', 'line', "UAA Client Secret:", '-i', \$client_secret); + + $self->vault->set("${vault_prefix}oauth", "provider_key", $client_id); + $self->vault->set("${vault_prefix}oauth", "provider_secret", $client_secret); + + info("". + "\nWhat is the URL of the CF installation that will be used for UAA-based". + "\nauthentication. Should be the same URL that is used to log in to the CF". + "\ninstallation."); + + my $cf_base_url; + prompt_for('cf_base_url', 'line', + "Cloud Foundry Base URL:", '-i', '-V', 'url', + \$cf_base_url); + + my $cf_scheme = $cf_base_url; + if ($cf_base_url =~ /^(https?):\/\/(.*)/) { + $cf_scheme = $1; + $cf_base_url = $2; + } else { + $cf_scheme = "https"; + } + + my $cf_api_url; + prompt_for('cf_api_url', 'line', + "Cloud Foundry API URL:", '-i', + "--default", "${cf_scheme}://api.system.${cf_base_url}", + '-V', 'url', + \$cf_api_url); + + $$params_ref .= " cf_api_url: $cf_api_url\n"; - return $self->done(); + info("". + "\nThe Cloud Foundry CA cert is used to authenticate Concourse to the UAA,". + "\nso that Concourse can then authorize users after they log into the UAA.". + "\nThis is usually something like '#C{secret/path/to/keys/for/haproxy/ssl:certificate}'". + "\nIf you are unsure, use '#G{safe tree}' to find it. If you are terminating ssl on LBs or". + "\nGo routers, you will need cert on those nodes."); + + my $cf_ca_cert_vault_path; + prompt_for('cf_ca_cert_vault_path', 'line', + "What is your CF CA cert path?", + '-V', 'vault_path_and_key', + \$cf_ca_cert_vault_path); + + $$params_ref .= " cf_ca_cert_vault_path: $cf_ca_cert_vault_path\n"; + + my @cf_spaces; + prompt_for('cf_spaces', 'multi-line', + "What CF spaces do you want to grant access to Concourse?", + \@cf_spaces); + + if (@cf_spaces) { + $$params_ref .= " cf_spaces:\n"; + for my $space (@cf_spaces) { + $$params_ref .= " - $space\n"; + } + } + } +} + +sub _configure_vault { + my ($self, $features_ref, $params_ref) = @_; + + my $use_vault; + prompt_for('use_vault', 'select', + "Vault integration for secret storage:", + '-o "[vault] Use static token"', + '-o "[vault-approle] Use approle (generate via genesis do setup-approle)"', + '-o "[] No secret storage integration"', + \$use_vault); + + if ($use_vault) { + if ($use_vault eq "vault-approle") { + push @$features_ref, "vault"; + } + push @$features_ref, $use_vault; + + my $vault_url; + prompt_for('vault_url', 'line', '-i', + "Vault URL:", + "--default", "$ENV{GENESIS_TARGET_VAULT}", + '-V', 'url', + \$vault_url); + + $$params_ref .= " vault_url: $vault_url\n"; + + my $vault_insecure_skip_verify; + prompt_for('vault_insecure_skip_verify', 'boolean', + "Allow insecure connection?", "--default", "yes", "--inline", + \$vault_insecure_skip_verify); + + if ($vault_insecure_skip_verify) { + $$params_ref .= " vault_insecure_skip_verify: true\n"; + } + + my $vault_path_prefix; + prompt_for('vault_path_prefix', 'line', '-i', + "Vault Path Prefix:", + "--default", '/concourse', + \$vault_path_prefix); + + if ($vault_path_prefix ne '/concourse') { + $$params_ref .= " vault_path_prefix: $vault_path_prefix\n"; + } + + if ($use_vault eq "vault") { + my $token; + prompt_for('vault:token', 'secret-line', "Vault Token", \$token); + } elsif ($use_vault eq "vault-approle") { + info(''. + "\nYou must run #C{$ENV{GENESIS_CALL_ENV} do -- setup-approle} before deploying to build the". + "\nconcourse approle."); + } + } +} + +sub _configure_external_db { + my ($self, $features_ref, $params_ref) = @_; + + my $use_external_db; + prompt_for('use_external_db', 'boolean', + "Do you want to use an external database?", "--default", "no", "--inline", + \$use_external_db); + + if ($use_external_db) { + push @$features_ref, "external-db"; + + my $external_db_host; + prompt_for('external_db_host', 'line', + "Enter the host of the database using IP or FQDN.", + \$external_db_host); + + $$params_ref .= " external_db_host: $external_db_host\n"; + + my $external_db_port; + prompt_for('external_db_port', 'line', + "The port that the database is listening on.", + "--default", "5432", + "--validation", "port", + \$external_db_port); + + if ($external_db_port ne "5432") { + $$params_ref .= " external_db_port: $external_db_port\n"; + } + + my $external_db_name; + prompt_for('external_db_name', 'line', + "The name of the database to connect to.", "--default", "atc", + \$external_db_name); + + if ($external_db_name ne "atc") { + $$params_ref .= " external_db_name: $external_db_name\n"; + } + + my $external_db_user; + prompt_for('external_db_user', 'line', + "The username used to connect to the database.", "--default", "atc", + \$external_db_user); + + if ($external_db_user ne "atc") { + $$params_ref .= " external_db_user: $external_db_user\n"; + } + + my $password; + prompt_for('database/external:password', 'secret-line', + "The password for the '$external_db_user' database user.", + \$password); + + my $external_db_sslmode; + prompt_for('external_db_sslmode', "select", + "The sslmode parameter to connect to the database with.", + "--default", "verify-ca", + "--option", "[disable] disable - No security, and no overhead for encryption.", + "--option", "[allow] allow - Only use SSL if the server insists on it.", + "--option", "[prefer] prefer - Use SSL if the server supports it.", + "--option", "[require] require - Use SSL but do not verify the certificate.", + "--option", "[verify-ca] verfiy-ca - Use SSL and verify the CA certificate.", + "--option", "[verify-full] verify-full - Use SLL and verify the certificate chain.", + \$external_db_sslmode); + + if ($external_db_sslmode ne "verify-ca") { + $$params_ref .= " external_db_sslmode: $external_db_sslmode\n"; + } + + if ($external_db_sslmode =~ /^verify-/) { + my $use_external_db_ca; + prompt_for('use_external_db_ca', 'boolean', + "Do you want to provide your own ca certificate for $external_db_sslmode mode?", + "--default", "no", "--inline", + \$use_external_db_ca); + + if ($use_external_db_ca) { + push @$features_ref, "external-db-ca"; + + my $external_db_ca; + prompt_for('external_db_ca', 'block', + "The sslmode ca certificate required for sslmode $external_db_sslmode.", + \$external_db_ca); + + $$params_ref .= " external_db_ca: |\n"; + for my $line (split /\n/, $external_db_ca) { + $$params_ref .= " $line\n"; + } + } + } + } +} + +sub _configure_tls { + my ($self, $features_ref) = @_; + + info("". + "\nConcourse should be protected by TLS, since build logs may contain". + "\nsensitive information (like IPs, usernames, etc.)."); + + my $ssl_cert_feature; + prompt_for('ssl_cert_feature', 'select', + "How would you like to configure Concourse TLS?", + '-o "[provided-cert] I have my own certificate for Concourse"', + '-o "[self-signed-cert] Please have Genesis create a self-signed certificate for Concourse"', + '-o "[no-tls] Do not; an upstream proxy / load balancer is handling TLS"', + \$ssl_cert_feature); + + push @$features_ref, $ssl_cert_feature; + + if ($ssl_cert_feature eq "provided-cert") { + my $certificate; + prompt_for('ssl/server:certificate', 'secret-block', + "What is the SSL certificate for Concourse?", + \$certificate); + + my $key; + prompt_for('ssl/server:key', 'secret-block', + "What is the SSL key for Concourse?", + \$key); + } +} + +sub _configure_external_domain { + my ($self, $params_ref) = @_; + + info("". + "\nThe external domain for concourse is the DNS entry users will use to access". + "\nConcourse. You can specify the IP address if you don't have a DNS entry. Do". + "\nnot include 'https://' in this value."); + + my $external_domain; + prompt_for('external_domain', 'line', "External Domain or IP:", '-i', + \$external_domain); + + $$params_ref .= " external_domain: $external_domain\n"; + + info("". + "\nThe main target is the name that will be used by fly to connect to the main". + "\nteam. This defaults to the name of the environment, but can be given a short-". + "\nhand name for convenience. Team-based targets will be the name of the team,". + "\nfollowed by @"); + + my $main_target; + prompt_for('main_target', 'line', "Master target name", '-i', "--default", "$ENV{GENESIS_ENVIRONMENT}", + \$main_target); + + if ($main_target ne $ENV{GENESIS_ENVIRONMENT}) { + $$params_ref .= " main_target: $main_target\n"; + } +} + +sub _write_environment_file { + my ($self, $filename, $features_ref, $params) = @_; + + open(my $fh, '>', $filename) or bail("Could not open $filename for writing: $!"); + + # Write the header + print $fh "---\n"; + print $fh "kit:\n"; + print $fh " name: $ENV{GENESIS_KIT_NAME}\n"; + print $fh " version: $ENV{GENESIS_KIT_VERSION}\n"; + print $fh " features:\n"; + print $fh " - (( replace ))\n"; + + # Write the features + for my $feature (@$features_ref) { + print $fh " - $feature\n"; + } + + # Write the genesis config block + my ($out, $rc) = run('genesis_config_block'); + print $fh $out; + + # Write the params + if ($params) { + print $fh "\nparams:\n$params"; + } + + close $fh; + + info("". + "\nWrote configuration to #C{$filename}."); } 1; + diff --git a/hooks/post-deploy.pm b/hooks/post-deploy.pm index e69de29..f83248d 100644 --- a/hooks/post-deploy.pm +++ b/hooks/post-deploy.pm @@ -0,0 +1,52 @@ +#!/usr/bin/env perl +# vim: set ts=2 sw=2 sts=2 foldmethod=marker +package Genesis::Hook::PostDeploy::Concourse v2.7.0; + +use strict; +use warnings; +use v5.20; # Genesis min perl version is 5.20 +use Genesis qw/info/; +use parent qw(Genesis::Hook::PostDeploy); + +sub init { + my ($class, %ops) = @_; + my $self = $class->SUPER::init(%ops); + $self->check_minimum_genesis_version('3.1.0-rc.20'); + return $self; +} + +sub perform { + my ($self) = @_; + + # Call any parent methods that need to be executed + $self->SUPER::perform() if $self->can('SUPER::perform'); + + # Only show deployment info if the deployment was successful + if ($self->deploy_successful) { + my $mode = $self->env->want_feature('workers') ? "Satellite (workers only)" : "Full"; + my $call_env = $self->env->get_call_path_with_env(); + + info( + "\n#M{$ENV{GENESIS_ENVIRONMENT}} $mode Concourse deployed!\n". + "\nFor details about the deployment, run\n". + "\t#G{$ENV{GENESIS_CALL} info $ENV{GENESIS_ENVIRONMENT}}\n". + "\nTo download the '#C{fly}' CLI for the first time, run\n". + "\t#G{$call_env do -- download-fly /location/in/your/path}\n". + "\nTo update your current version of the '#C{fly}' CLI, run\n". + "\t#G{$call_env do -- download-fly --sync}\n". + "\nTo target & log into this Concourse with fly, run\n". + "\t#G{$call_env do -- login}\n". + "\nTo run a fly command against this Concourse, run (without -t )\n". + "\t#G{$call_env do -- fly [options and arguments]}\n". + "\nAs an extra efficiency, if you run the '#C{fly}' addon above, it will\n". + "\tautomatically create the target if one doesn't exist, and log you in if\n". + "\tyou're not already logged in, before running your desired command.\n". + "\nFinally, to visit the Concourse Web Portal, run:\n". + "\t#G{$call_env do -- open}\n" + ); + } + + return $self->done(); +} + +1; diff --git a/ocfp/full-concourse-vars-override-stackit.yml b/ocfp/full-concourse-vars-override-stackit.yml new file mode 100644 index 0000000..a6561c6 --- /dev/null +++ b/ocfp/full-concourse-vars-override-stackit.yml @@ -0,0 +1,48 @@ +--- +meta: + ocfp: + env: + cloud: (( concat genesis.env "." genesis.type "." )) + +params: + external_domain: (( vault meta.ocfp.vault.config "/fqdns:concourse" )) + + num_web_nodes: 1 + worker: 3 + + concourse_network: (( concat meta.ocfp.env.cloud "net-concourse" )) + concourse_disk_type: (( concat meta.ocfp.env.cloud "disk-concourse" )) + concourse_vm_type: (( concat meta.ocfp.env.cloud "vm-concourse" )) + worker_vm_type: (( concat meta.ocfp.env.cloud "vm-concourse-worker" )) + + availability_zones: + - (( concat genesis.env "-z1" )) + - (( concat genesis.env "-z2" )) + - (( concat genesis.env "-z3" )) + +exodus: + tsa_host: (( grab params.external_domain )) + +--- +- type: replace + path: /instance_groups/name=worker/networks/0/name + value: (( grab params.concourse_network )) + +- type: replace + path: /instance_groups/name=web/networks/0/name + value: (( grab params.concourse_network )) + +- type: replace + path: /instance_groups/name=web/azs + value: + - (( concat genesis.env "-z1" )) + +- type: replace + path: /instance_groups/name=worker/azs + value: + - (( concat genesis.env "-z2" )) + - (( concat genesis.env "-z3" )) + +- type: replace + path: /instance_groups/name=web/networks/0/static_ips + value: [ 10.4.4.7 ] diff --git a/ocfp/full-concourse-vars-override.yml b/ocfp/full-concourse-vars-override.yml index 8c775d5..545af69 100644 --- a/ocfp/full-concourse-vars-override.yml +++ b/ocfp/full-concourse-vars-override.yml @@ -1,6 +1,6 @@ --- params: - external_domain: (( vault meta.ocfp.vault.tf "/lbs/concourse:domain" )) + external_domain: (( vault meta.ocfp.vault.config "/lbs/concourse:domain" )) num_web_nodes: 1 worker: 3 diff --git a/ocfp/ocfp.yml b/ocfp/ocfp.yml index 754c62c..886d60e 100644 --- a/ocfp/ocfp.yml +++ b/ocfp/ocfp.yml @@ -3,9 +3,11 @@ meta: ocfp: env: scale: (( grab params.ocfp_env_scale || "dev" )) + cloud: (( concat genesis.env "." genesis.type "." )) vault: - tf: (( concat genesis.secrets_mount "tf/" genesis.vault_env )) + config_prefix: (( grab params.ocfp_vault_config_prefix || "config/" )) + config: (( concat genesis.ocfp_config_mount genesis.ocfp_env )) certs: trusted: diff --git a/ocfp/vault-vars-override.yml b/ocfp/vault-vars-override.yml index d003223..483b30b 100644 --- a/ocfp/vault-vars-override.yml +++ b/ocfp/vault-vars-override.yml @@ -1,10 +1,10 @@ --- params: - vault_url: (( vault meta.vault "/vault:url")) + vault_url: (( vault meta.vault "/approle/concourse:url" )) # was (( vault meta.vault "/vault:url")) vault_token: ~ vault_auth_backend: approle - vault_approle_role_id: (( vault meta.vault "/vault:approle_role_id")) - vault_approle_secret_id: (( vault meta.vault "/vault:approle_secret_id")) + vault_approle_role_id: (( vault meta.vault "/approle/concourse:approle-id" )) # was (( vault meta.vault "/vault:approle_role_id")) + vault_approle_secret_id: (( vault meta.vault "/approle/concourse:approle-secret" )) # was (( vault meta.vault "/vault:approle_secret_id")) --- meta: