This CI/CD pipeline example can be installed by running various cloud-formation scripts listed
below.
Note that rather than using a single script this example uses multiple scripts to establish a
separation of concerns (networking, security, application) to reflect a separation of these
concerns that will be in effect in most organizations.
Here the separation is used to demonstrate a scalable solution to this separation of concerns.
In a 'real world' secnario these cloud-formation scripts would be separated into separate
git
repositories. However, here they are combined for convenience.
To provide scalability and consistency, all cloud-formation scripts have a set of common
parameters, such as: System
name, Environment
name, AWS Region
,
and (optional) S3 Provisioning Bucket
.
name. Furthermore they may have specific values, such as CIDRBlock
, Instance AMI
and
Instance Size
.
Those scripts run additional EC2 initialization/provisioning use an S3 bucket to
share information, e.g. files to be installed on an EC2 instance,
The various cloud formation stacks follow a naming conventions that is adhered to when creating objects in the S3 bucket. The structure is used to ensure access rights are set approrpiately for the AWS IAM Policies that are generated by the security scripts.
The expected 'directory' structure is as follows:
<bucket>/cloud-formation/<system>/<environment>/<stack>/<stack-resources>
One of the advantage of taking this structured approach (common parameters, S3 structuring) is that not only the application pipeline can be fully automated, the infrastructure can easily be automated using a development pipeline as well, building on the Automated Installation script.
- You need to have an AWS account with rights to create/delete/modify AWS S3, VPC, IAM, EC2, and ECS resources.
- Create (or import) an AWS EC2 keypair with the name
<system>-shared-jenkins
and save the private key. where Jenkins is the value of the parameter given to the cloud formation scripts. - Create (or import) an AWS EC2 keypair with the name
<system>-dev-helloworld
and save the private key. - Create (or import) an AWS EC2 keypair with the name
<system>-test-helloworld
and save the private key. - Create (or import) an AWS EC2 keypair with the name
<system>-prod-helloworld
and save the private key. - Create an AWS S3 bucket for AWS resource provisioning.
Note that:
-
The name is relevant, because the cloud-formation scripts, rather than having the named passed to it, internally builds the key pair name from the
System
andEnvironment
parameters. The advantage is that this enforces a consistent naming sructure and eliminates the need for passing the key pair names. -
The default values for is
dso
(default for cloud-formation parameters), so when you plan to use the default create a key pairs using dso for , e.g.dso-shared-jenkins
.
-
Install the AWS-CLI
-
Setup programmatic access
-
Setup an AWS profile
-
Run install.sh
bin/install.sh --bucket your-bucket-name --profile your-aws-profile-name
Optionally add
--system your-system-name
if not using the default (i.e.dso
) -
Continue with Jenkins Configuration
To install the common AWS VPC and IAM resources run the following cloud-formation scripts:
- Run the cloud-formation script in network/shared using the listed policy
- Run the cloud-formation script in security/shared using the listed policy
Repeat these steps passing dev
, test
, and prod
as the parameter values for the
Environment
parameter in the various stacks:
-
Run the cloud-formation stack in network/hello-world using the listed policy Pass a value for parameter:
Environment
. Eitherdev
,test
, orprod
ApplicationCIDRBlock
. This CIDR Block must fall within the VPCCIDRBlock
, e.g.172.27.x.0/24
(where x is 10 fordev
, 20 fortest
and 30 forprod
).
-
Run the cloud-formation stack in security/hello-world using the listed policy Pass a value for parameter:
Environment
. Eitherdev
,test
, orprod
VPCCIDRBlock
that matches the earlier value (see common AWS Resources).PrivilegedCIDRx
(for x=1..5) to enable access to resources on the private Jenkins subnet. At least one privileged address is needed to be able to obtain the initial Jenkins administrator password.
-
Run the cloud-formation stack in helloworld/app using the listed policy Pass a value for parameter:
Environment
. Eitherdev
,test
, orprod
.SecurityContext
. Match the value ofEnvironment
.
To verify that application instance is install, browse to
http://<PublicIP output value of Stack>
. Initially the provisioning scripts sets up a web application using the kitematic/hello-world-nginx docker image. Once the pipeline is executed this application will be automatically replaced with the actual hello world application using the [Docker image[../Dockerfile)]
Once completed you should something similar to the following in your AWS Cloud Formation console:
To install the the AWS resources for the Jenkins instance run the following cloud-formation scripts and subsequently configure Jenkins:
-
Run the cloud-formation script in network/jenkins using the listed policy
-
Run the cloud-formation script in security/jenkins using the listed policy Pass a value for parameters:
PrivilegedCIDRx
(for x=1..5) to enable access to resources on the private Jenkins subnet. At least one privileged address is needed to be able to obtain the initial Jenkins administrator password.
-
Run the cloud-formation script in jenkins/app using the listed policy Jenkins is configured to run inside Docker. Furthermore, in this example setup the Jenkins instance also runs SonarQube inside. Docker Pass a value to parameters:
-
Continue with Jenkins Configuration
Once the infrastructure is created Jenkins must be configured using the following steps:
-
Get the initial administrator password: a. When using the automated installation, the password wil be available in the stack outputs under Output
JenkinsInitialPassword
the value shows a json formatted value:{"<instance-id>":"<password>"}
b. When using the manual installation:- Login to the Jenkins EC2 instance with the jenkins key
ssh -i dso-shared-jenkins <jenkins-public-ip>
- Get the key from
/var/jenkins_home/secrets/initialAdminPassword
- Login to the Jenkins EC2 instance with the jenkins key
-
Browse to Jenkins at
http://<jenkins-public-ip>
and use the initial password to login -
Choose to install the suggested plugins
-
Configure a new admin user, i.e. do not continue with the initial admin, such that the exposed initial password is no longer valid.
-
(Optional) Setup matrix-based security under
Manage Jenkins > Configure Global Security > Access Control > Authorization
:- Select
Matric-based security
- Add your newly create admin user and assign all privileges
(or you will no longer be able to manage Jenkins)
By default Jenkins Authozization is setup with
Logged-in users can do anything
.
- Select
-
Next install the SSH and SSH Agent plugins under
Manage Jenkins > Manage Plugins
-
Create the following credentials under
Credentials > global
- (Optional, if repo is private) A valid Git credential (ID:
*any*
) - An SSH Username with private key for the hello-world dev app instance
(ID:
dso-dev-helloworld
, Username:ec2-user
) using the private key from the matching EC2 Keypair - An SSH Username with private key for the hello-world test app instance
(ID:
dso-test-helloworld
, Username:ec2-user
) using the private key from the matching EC2 Keypair - An SSH Username with private key for the hello-world prod app instance
(ID:
dso-prod-helloworld
, Username:ec2-user
) using the private key from the matching EC2 Keypair
f. Add users as appropriate under
Manage Jenkins > Manage Users
- (Optional, if repo is private) A valid Git credential (ID:
-
Setup the multi-pipeline job for the Hello World Application
a. Create the job using
New Item
with item namehello-world-app
and theMultibranch pipeline
option b. Specify aHello World application
as the Display Name (Optional) c. Add your GitHub repo as a source using theAdd Source
button with the GitHub option - Select the github Credentials ID you created earlier (if repo is private) - Enter your organization's name, e.g.boozallen
- Select the repository, e.g.devsecops-example-helloworld
- Select theSave
buttonYour Jenkins pipeline should now start to discover the branches in the designated repository, and once it discovers the Jenkinsfile, start to build all branches found. If you have multiple branches and want to limit building to the
master
branch you can add aFilter by Name
Behavior before selectingSave