2

Serverless Okta JWT as AWS API Gateway Authorizer

About this solution

In todays technological world it has become very popular ( and quite easy )Β  to create serverless architectures with Lambdas and expose them via API gateway.

The expose part is something which we could protect better.Β Solution provided here is basic blueprint which leverages openIDΒ  ( in this case set up in Okta ).

 

This software/code is provided to you “as-is” and without warranty of any kind, express, implied or otherwise, including without limitation, any warranty of fitness for a particular purpose

 

Tech involved

Although I would like to keep the solution requirements to minimal this requires some software/services to work nicely together. Below I highlighted what we will need:

  • Serverless – to create and configure the stack
  • AWS – to consume provided services
  • Okta –Β  to provide authentication

Setting up the solution

Setup of the whole solution involves several steps. I have outlined them below.

Create Okta openID application

  • Login to your Okta organisation and navigate to Admin part
  • Go to application
  • Create new native application with `openID` ( this is the only option atm)
  • For the name we have used `OpenID – myAppName`
  • For the redirect URL you can paste `api://myAppName` ( or anything else if you plan to use different kind of flows )
  • Save the app and proceed to editing.
  • In general settings you need to modify *Allowed grant types* and enable `Resource Owner Password`
  • In client credentials enable `Use Client Authentication` ( make note of Client Id and Client Secret)
  • Assign the application to engineers which should have access to REST API

As a nice feature you could use claims to determine who can execute READ/WRITE actions

 

Verify token generation

In order to verify that you can get tokens from the app you have just created you need to call one of Okta endpoints.

  • Create basic authentication credentials consisting of the following client_id:client_secret
    You can use the following snippet

    echo "client_id:client_secret" | base64
  • With the result of that command you will be able to make calls using Authorization: Basic <result-of-command>
  • Make http call to token endpoint , making sure to replace your-okta-tenant-name , username and password
    curl -X POST \
    https://xxx.okta-emea.com/oauth2/default/v1/token \
    -H 'Authorization: Basic MG9hMmQzN.........Q==' \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d 'username=username&password=Password1&grant_type=password&scope=openid'

     

  • In response you should receive token in the following format
    {
    "access_token": "eyJraW.....BvXdkU2Gg",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid",
    "id_token": "eyJr....yg"
    }

 

Obtain a public key from Okta

At this point of time you have fully working Okta openID app and can obtain tokens. Our next task is to obtain public key which will allow us in later stage to verify token signature.

There are many ways to obtain the key – and each of them can either take more time or involves you providing more information. My idea was simple – automate it as much as possible… therefore I came up with go-jwk-pem (available in github ) . It is a simple CLI tool which takes either token or Okta server URL and retrieves public key which have been used to sign the JWT.

In this instance I will just use token from previous step

go-jwk-pem from-token --token eyJraW.....BvXdkU2Gg | /usr/bin/env ruby -e 'p ARGF.read'

Result of this command is single line public key , which is last piece of our puzzle which we need to make our solution working.

"-----BEGIN RSA PUBLIC KEY-----\nMIIBIjA........A4\nzTsuZ+eQLfhNbuA.....wWtcDsd+vMUlS7iJow\n2QIDAQAB\n-----END RSA PUBLIC KEY-----\n\n"

 

Creating AWS stack using Serverless

The time has come when we begin real fun πŸ™‚ Let’s begin by cloning the solution from Github

One thats done we need to modify value in serverless.env.yml

dev:
  OKTA_PUBLIC_KEY: <PASTE-YOUR-PEM-HERE>

Since this functions are written in go we need to build them before deploying

> [SHELL]  RafPe $ make build
env GOOS=linux go build -ldflags="-s -w" -o bin/func1 func1/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/auth auth/main.go

And now let’s deploy by using one of profile for AWS ( from credentials file ) running simple command

> [SHELL]  RafPe $ sls deploy -s dev --aws-profile myAwsProfile --verbose

Output shows us details about our functions deployes ( this is minimal blueprint so your output can have more )

Serverless: Stack update finished...
Service Information
service: test-auth
stage: dev
region: eu-west-1
stack: test-auth-dev
api keys:
  None
endpoints:
  ANY - https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello
functions:
  func1: test-auth-dev-func1
  auth: test-auth-dev-auth
layers:
  None

Stack Outputs
AuthLambdaFunctionQualifiedArn: arn:aws:lambda:eu-west-1:123:function:test-auth-dev-auth:3
Func1LambdaFunctionQualifiedArn: arn:aws:lambda:eu-west-1:123:function:test-auth-dev-func1:3
ServiceEndpoint: https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev
ServerlessDeploymentBucketName: test-auth-dev-serverlessdeploymentbucket-2g5ap50n5lwn

 

Testing the solution

So right now we can immediately check if our setup works. Let’s start by trying to make simple http call

> [SHELL]  RafPe $ http https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Date: Sat, 15 Dec 2018 11:01:24 GMT
Via: 1.1 bce55e537f8dfcf0127f649d11fd1821.cloudfront.net (CloudFront)
{
    "message": "Unauthorized"
}

As expected we getΒ Unauthorized message πŸ˜‰ Let’s try to add token generated by Okta and make the call again

> [SHELL]  RafPe $ http https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello Authorization:'Bearer eyJraWQ....QHMi5ISw'
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 69
Content-Type: application/json
Date: Sat, 15 Dec 2018 11:10:04 GMT
Via: 1.1 94d63cbf92082237b86267ffd4cacc64.cloudfront.net (CloudFront)
X-Cache: Miss from cloudfront
X-MyCompany-Func-Reply: world-handler
{
    "message": "Okay so your other function also executed successfully!"
}

and voilla πŸ˜‰ we have just created custom authorizer validating our Okta JWT.

 

Summary

Although this is just a blueprint it can be nicely extended.Β I would like to point out several items you might be interested about this

  • Solution can be nicely extended to use claims to provide appropriate access – I find it really nice
  • You are not limited to use goLang because of one function being written in go. Serverless support multiple frameworks – just need to define those on function level then ( and define what packages you are including )

 

if you have any feedback – please leave comment or add your code into github repo πŸ˜‰

 

 

 

0

Github – update forked repository with upstream changes

Sometimes when you work with projects on Github it takes you a bit more time than expected to prepare solution which you are happy to create PR for. In those cases it is good to be able to pull changes from upstream

1. Start by cloning your fork

git clone [email protected]:YOUR-USERNAME/YOUR-FORKED-REPO.git

2. Add remote from original repository in your forked repository:

cd into/cloned/fork-repo
git remote add upstream git://github.com/ORIGINAL-DEV-USERNAME/REPO-YOU-FORKED-FROM.git
git fetch upstream

3. Updating your fork from original repo to keep up with their changes:

git pull upstream master

From this moment you can continue your happy coding with changes

1

Automating Akamai – Network lists with CLI and API

Hi,

This most likely can be first of several posts on tools and approach taken to automate tasks in Akamai. Before we look into specific toolset lets peakΒ what is Akamai’s vision on automation

 

From what I have seen some of the features do work nicely and some of them are still in betaΒ or alpha. We will be focusing on Akamai CLIΒ and extending it with plugin to manage network lists.Β 

Akamai CLI is a tool which allows us to write plugin in most of common languages ( for me it will be Golang ) and then use it from console. Since the tool is well documented I will skip introducing it and send you off to documentationΒ 

 

Choosing your client

Before you go ahead and write your own plugin you should decide on which client to choose ( or write your own ) which will take over communication with Akamai’s API.

For Golang Akamai have client which you can get hereΒ – however inspired by colleague of mine who wrote go-gitlabΒ ( and not only ) I decided to make client a bit more robust and organised and came up ( as we engineers usually do πŸ™‚ ) with alternative version.

This client can be found underΒ https://github.com/RafPe/go-edgegrid

 

Akamai-CLI Network Lists

We start off by installing the plugin into Akamai’s CLI toolkit by running

akamai install https://github.com/RafPe/akamai-cli-netlist

which in return shows us the output similar to

 

From this point onwards we can use of all benefits of our new plugin. Just to give it a spin I will tryΒ explore just getting the lists …

Getting all network lists

 

Getting one list with all elements

 

Want more ?….

Rest of them is well documented in repository page underΒ https://github.com/RafPe/akamai-cli-netlistΒ  and from thereΒ I encourage you to explore the options you have for automation and let me know in comments did it work for you πŸ™‚

 

 

More community extension

My extension is now not the only one recently created – below is the list of other ones which you can make use of already

Akamai CLI for Netstorage https://github.com/partamonov/akamai-cli-netstorage
Akamai CLI for Siteshield https://github.com/partamonov/akamai-cli-siteshield
Akamai CLI for Firewall Rules Notifications https://github.com/partamonov/akamai-cli-frn

1

Serverless REST api for Amazon SNS

Hi!

So it has been a while since I posted some technical posts. Now … this is something that touches us all – the lack of time in the jungle of ongoing projects πŸ™‚ However today we will look into something which I find quite nice for developing of new applications.

Solution is based on serverless framework. Now before we go on – we all know that serverlessΒ is a nice catchy word for ‘someone’ else computer and operation problem :)’Β . But idea is simple – I’m using AWS – and there it spins me up lambda functions with associated API gateway.

I decided to create this solution to have unified way of deploying and interacting with AWS services in a way that would be easiest for me to consume. However for someone else it might not be the best. Also to be on safe side – this code is really version v1.0.0 so it willΒ get updates as we go ( PR always welcome )

The repository for this write up is available underΒ https://github.com/RafPe/serverless-api-sns

Solution folder structure

The solution structure is created as follows

total 32
-rw-r--r--    1 rafpe  450652656   1.0K Sep  9 17:49 LICENSE
-rw-r--r--    1 rafpe  450652656   2.2K Sep 10 15:07 README.md
drwxr-xr-x  234 rafpe  450652656   7.8K Sep  5 23:53 node_modules
-rw-r--r--    1 rafpe  450652656   255B Sep 10 14:19 package.json
-rw-r--r--    1 rafpe  450652656   3.6K Sep 10 14:21 serverless.yml
drwxr-xr-x    7 rafpe  450652656   238B Sep 10 14:52 sns

and the SNS folder:

β”œβ”€β”€ attributes
β”œβ”€β”€ endpoint
β”‚Β Β  β”œβ”€β”€ create.js
β”‚Β Β  β”œβ”€β”€ delete.js
β”‚Β Β  └── list.js
β”œβ”€β”€ messages
β”œβ”€β”€ models
β”‚Β Β  └── endpoint.create.json
└── topics

Code

Now since this is not a coding school and I have used really simple code I will not be going into details there. I just might say code has someΒ portions which are repeated and could be wrapped into common methods πŸ˜‰ did not have time to take a look into that one yet.

For the rest it is using standard aws libraries to execute most of the actions

 

Serverless.yml

Is the heart of your deployment. It describes what will be created and how to link those things together. For more advanced examples you should check out docs.serverless.com

# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: api

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

provider:
  name: aws
  role: xmyCustRole1 
  apiKeys:
    - myApiKey  
  runtime: nodejs6.10
  region: eu-west-1  


stage: dev



functions:
  create:
    handler: sns/endpoint/create.create
    events:
      - http:
          path: endpoint/create
          method: post
          cors: true
          private: true

  delete:
    handler: sns/endpoint/delete.delete
    events:
      - http:
          path: endpoint/delete
          method: delete
          cors: true
          private: true


  list:
    handler: sns/endpoint/list.list
    events:
      - http:
          path: endpoint/list
          method: post
          cors: true
          private: true


resources:
  Resources:
    # PetsModelNoFlatten: 
    #   Type: "AWS::ApiGateway::Model"
    #   Properties: 
    #     RestApiId: {Ref: ApiGatewayRestApi}
    #     ContentType: "application/json"
    #     Description: "Schema for Pets example"
    #     Name: "PetsModelNoFlatten"
    #     Schema: 
    #       Fn::Join: 
    #         - ""
    #         - 
    #           - "{"
    #           - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
    #           - "   \"title\": \"PetsModelNoFlatten\","
    #           - "   \"type\": \"array\","
    #           - "   \"items\": {"
    #           - "       \"type\": \"object\","
    #           - "       \"properties\": {"
    #           - "           \"number\": { \"type\": \"integer\" },"
    #           - "           \"class\": { \"type\": \"string\" },"
    #           - "           \"salesPrice\": { \"type\": \"number\" }"
    #           - "       }"
    #           - "   }"
    #           - "}"
    xmyCustRole1:
      Type: AWS::IAM::Role
      Properties:
        Path: /my/cust/path/
        RoleName: xmyCustRole1
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: myPolicyName
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*:*:*
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                  Action:
                    - sns:CreatePlatformEndpoint
                  Resource: arn:aws:sns:*:*:*           
                - Effect: "Allow"
                  Action:
                    - "s3:PutObject"
                  Resource:
                    Fn::Join:
                      - ""
                      - - "arn:aws:s3:::"
                        - "Ref" : "ServerlessDeploymentBucket"

                        

 

IAM policy

To make this all a bit more secure I defined here my specific IAM Role with custom permissions for actions – So if you would need to extend permisions required you would need to look into that resources as well

 

Validations

In my code you will find that I validate if specific parameters are received from the request. Now this is again something that

  1. Could be done better by taking this logic out into common functions or …
  2. even better to use the API gateway validators

I therefore went ahead and created my self json schema using the following online schema generator. With that one done I had to ‘escape’ those characters and then create a policy using serverless resource

resources:
  Resources:
    PetsModelNoFlatten: 
      Type: "AWS::ApiGateway::Model"
      Properties: 
        RestApiId: {Ref: ApiGatewayRestApi}
        ContentType: "application/json"
        Description: "Schema for Pets example"
        Name: "PetsModelNoFlatten"
        Schema: 
          Fn::Join: 
            - ""
            - 
              - "{"
              - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
              - "   \"title\": \"PetsModelNoFlatten\","
              - "   \"type\": \"array\","
              - "   \"items\": {"
              - "       \"type\": \"object\","
              - "       \"properties\": {"
              - "           \"number\": { \"type\": \"integer\" },"
              - "           \"class\": { \"type\": \"string\" },"
              - "           \"salesPrice\": { \"type\": \"number\" }"
              - "       }"
              - "   }"
              - "}"

This is all nice but the problem I experience now is that I cannot in programatic way find out how to apply required body validators to specific methods using serverless. Maybe something I will find out later.

 

Deploying

Deploying is easy as running

serverless deploy

and the output should look like

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (37.84 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..........................
Serverless: Stack update finished...
Service Information
service: api
stage: dev
region: eu-west-1
api keys:
  myApiKey: ID0d9P4Vgi82l2YvndLwi81FA63lCup1adNQX7eD
endpoints:
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/create
  DELETE - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/delete
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/list
functions:
  create: api-dev-create
  delete: api-dev-delete
  list: api-dev-list

 

Fun

Now this is the part I like the most πŸ™‚ Fun starts here when you play around with the working solution. If you got any feedback I would be more than happy to hear about it.

 

 

 

 

 

0

Letsencrypt – simple renew in bash

Simple renew routine in bash to renew certificates with let’s encrypt.

#!/bin/sh
for domain in $RENEWED_DOMAINS; do
        cat "$RENEWED_LINEAGE/privkey.pem" "$RENEWED_LINEAGE/fullchain.pem" > "/etc/ssl/certs/haproxy/${domain}.pem"
done

And then just call renew using certbot and specify script asΒ parameter for renew

certbot renew --quiet --renew-hook /scripts/renew-hook-pem.sh >/dev/null 2>&1

 

Add this to your crontab and ur done! Doing it differently ? Share in comments!

 

6

Gitlab – custom pre-receive hook

As many of you I’m also using Gitlab to manage some of my projects. What I have recently been doing – was discovering how great it is to enable pipeline within your projects.

That have enabled me to install several runners and configure different stages of deployments for my repositories. While this all sounds cool it relies on single file called .gitlab-ci.yml

This would not be a big problem if not the fact that some of repositories have other developers working on it and potentially changing that file could present a security risk for my services/servers. So to overcome this I have come up with pre-receive hook that is now sort of ACL for my file unless secret commit message is included.

Installing

In repository create folder called *custom_hooks* i.e.

/var/opt/gitlab/git-data-disk/repositories/rafpe/ci-test.git/custom_hooks

Then create file called *pre-receive* and apply permissions to it

chmod +x pre-receive
chown git.git pre-receive

AfterwardsΒ you can just select the language you are interested in programming your custom git hook – below is my Ruby attempt.

What it does it check if thr push is not by any chance unathorised change to our gitlab-ci.yml file.

You would be able to change this file if your commit message will be done with specific secret. But I leave this forΒ ppl to adapt for their needs.

Script

#!/usr/bin/env ruby

our_secret = "aaaa"

params = gets
oldref = params.split()[0]
newref = params.split()[1]
refname = params.split()[2]



changed_files = `git diff --no-commit-id --name-only #{oldref}..#{newref}`

            if changed_files.include? '.gitlab-ci.yml' then
            commit_messages = `git log --pretty=%s #{newref} | head -3`.split("\n")

             unless commit_messages.include? our_secret
              puts "================================================================= "
              puts "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—     β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  "
              puts "β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•     β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— "
              puts "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—       β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘        β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘ "
              puts "β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆ   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘        β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘ "
              puts "β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• "
              puts "β•šβ•β•  β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β• β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β•   β•šβ•β•   β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•  "
              puts "================================================================= "
              puts ""
              puts " oldref is #{oldref}"
              puts " newref is #{newref}"
              puts " refname is #{refname}"
              puts " "
              puts " We are very very sorry but your change seems to violate our policies! "
              puts " Please check with your nearest Guru! :) ERROR_ID: 001        "
              puts "================================================================= "

              exit 1
             end

            end # if changed_files.include?
exit 0

 

I hope this will get you going and leave comments if you make some interesting changes to it πŸ™‚

0

Scaffolding application templates using Yeoman.io

When you quickly need to create a new application starting every time from scratch can be a pain in the back side πŸ™‚ But have no fear – there is a really nice service calledΒ YeomanΒ available here ( Β http://yeoman.io/ ).

Based on community best practices it acts as “generator” of all what you need to start your new app. Since I’m using MacBook nowadays – we will go through installation on that platform.

brew install node

 

Once done we can install yeoman and for demo purposes generator for hubot

npm install -g yo generator-hubot

 

And there it is πŸ˜‰ from this point onwards we just just create our apps – easily and whats most import on demand! Let’s start with something simple like Hubot

mkdir myhubot
cd myhubot
yo hubot

yeoman_hubot

 

 

 

And off it goes πŸ˜‰ Now the possibilities are broader and for example starting with c# app or angular is as easy as discovering them here ( generators ) . Try it πŸ™‚

0

Vagrant – create multiple VMs with multiple network interfaces

Today I will just share with you piece of code that I quite often used when I was working with quick spin of machines using Vagrant. It will create 2 VMs for you based on your requirements ( can be more if you modify the code πŸ™‚ )

 

# based on http://stackoverflow.com/a/33789603/2476347
servers=[
  {
    :hostname => "uno",
    :ip => "192.168.100.10",
    :box => "lamudi/centos-7.0",
    :ram => 1024,
    :cpu => 2
  },
  {
    :hostname => "duo",
    :ip => "192.168.100.11",
    :box => "lamudi/centos-7.0",
    :ram => 1024,
    :cpu => 2
  }
]

Vagrant.configure(2) do |config|
    servers.each do |machine|
        config.vm.define machine[:hostname] do |node|
            node.vm.box = machine[:box]
            node.vm.hostname = machine[:hostname]
            node.vm.network "private_network", ip: machine[:ip]
            node.vm.network "public_network", type: "dhcp", bridge: "en0: Wi-Fi (AirPort)"
            node.vm.provider "virtualbox" do |vb|
                vb.customize ["modifyvm", :id, "--memory", machine[:ram]]
              end
            end
          end    
end

 

The code as usual has a lot of potential for upgrade πŸ™‚ if you would have any suggestions please leave comments!