Skip to content

Commit

Permalink
Merge pull request #7 from jimbydamonk/master
Browse files Browse the repository at this point in the history
Code restructure and documentation update
  • Loading branch information
jimbydamonk authored Jul 20, 2016
2 parents aabb53d + 5b5dd40 commit a23e881
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 48 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
# mock-ec2-metadata [![Build Status](https://travis-ci.org/NYTimes/mock-ec2-metadata.svg?branch=master)](https://travis-ci.org/NYTimes/mock-ec2-metadata)


A simple service to mock the ec2 metadata service.
A simple service (written in [go](https://golang.org/) using [gizmo](https://github.com/NYTimes/gizmo)) to mock the ec2 metadata service. This is usefully for development images (like vagrant or packer) that require Instance base IAM permission or other metadata information.

For example, [cob](https://github.com/henrysher/cob) and [s3-iam](https://github.com/seporaitis/yum-s3-iam) can both use s3 as a yum repo. Both of these systems rely on the instances the proper credentials to have authorization to the s3 repos that yum uses.


The metadata service normal listens on a special private ip address `169.254.169.254`. This is a special address that will not exist on your system. One option is to bind an alias to the loopback iterface. This can be done with the following command:

```console
/sbin/ifconfig lo:1 inet 169.254.169.254 netmask 255.255.255.255 up
```

Many services assume that use the metadata service uses a default port 80 and do not allow configuration or override. A simple IP talbes rule and IP forwarding can get around that, as follows:

```console
$ echo 1 > /proc/sys/net/ipv4/ip_forward
$ iptables -t nat -A OUTPUT -p tcp -d 169.254.169.254/32 --dport 80 -j DNAT --to-destination 169.254.169.254:8111
$ service iptables save
```

## Configuration
All configuration is contained in either `./mock-ec2-metadata-config.json` or `/etc/mock-ec2-metadata-config.json`, the former overriding the latter.

Currently the support URLs for the metadata service are:

* http://169.254.169.254/latest/meta-data/latest/
* http://169.254.169.254/latest/meta-data/latest/meta-data/hostname
* http://169.254.169.254/latest/meta-data/latest/instance-id
* http://169.254.169.254/latest/meta-data/latest/instance-type
* http://169.254.169.254/latest/meta-data/latest/iam/security-credentials


## Getting started

Expand Down
15 changes: 0 additions & 15 deletions config.json

This file was deleted.

12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package main

import (
"os"

"github.com/NYTimes/gizmo/config"
"github.com/NYTimes/gizmo/server"
"github.com/NYTimes/mock-ec2-metadata/service"
)

func main() {
// showing 1 way of managing gizmo/config: importing from a local file
var cfg *service.Config
config.LoadJSONFile("./config.json", &cfg)

if _, err := os.Stat("./mock-ec2-metadata-config.json"); err == nil {
config.LoadJSONFile("./mock-ec2-metadata-config.json", &cfg)
} else if _, err := os.Stat("/etc/mock-ec2-metadata-config.json"); err == nil {
config.LoadJSONFile("/etc/mock-ec2-metadata-config.json", &cfg)
} else {
server.Log.Fatal("unable to locate config file")
}

server.Init("mock-ec2-metadata", cfg.Server)
err := server.Register(service.NewMetadataService(cfg))
Expand Down
24 changes: 24 additions & 0 deletions mock-ec2-metadata-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"Server": {
"HTTPPort": 8111,
"Log": "./mock-metadata-app.log",
"HTTPAccessLog": "./mock-metadata-access.log"
},

"MetadataValues" : {
"hostname": "mock-hostname",
"instance-id": "mock-instance-id",
"instance-type": "mock-instance-type",
"security-credentials": {
"user": "mock-user",
"AccessKeyId" : "mock-access-key",
"SecretAccessKey" : "mock-secret-key",
"Token": "mock-token",
"Expiration": "2112-12-31T11:59:59Z"
}
},
"MetadataPrefixes": [
"/2009-04-04/meta-data",
"/latest/meta-data"
]
}
154 changes: 125 additions & 29 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
package service

import (
"github.com/NYTimes/gizmo/server"
"github.com/Sirupsen/logrus"
"fmt"
"strings"
"encoding/json"
"net/http"

"github.com/NYTimes/gizmo/server"
"github.com/NYTimes/gizmo/web"
)

type (
SecurityCredentials struct {
User string `json:"User"`
AccessKeyId string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
Token string `json:"Token"`
Expiration string `json:"Expiration"`
}

MetadataValues struct {
Hostname string `json:"hostname"`
InstanceId string `json:"instance-id"`
InstanceType string `json:"instance-type"`
SecurityCredentials SecurityCredentials `json:"security-credentials"`
}

Config struct {
Server *server.Config
MetadataItems map[string]interface{}
Server *server.Config
MetadataValues *MetadataValues
MetadataPrefixes [] string
}
MetadataService struct {
config *Config
Expand All @@ -24,44 +44,120 @@ func (s *MetadataService) Middleware(h http.Handler) http.Handler {
return h
}

func (s *MetadataService) JSONMiddleware(j server.JSONEndpoint) server.JSONEndpoint {
return func(r *http.Request) (int, interface{}, error) {
func (s *MetadataService) GetHostName(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint(s.config.MetadataValues.Hostname)
fmt.Fprintf(w, res)
server.Log.Info("GetHostName returning: ", res)
return
}

func (s *MetadataService) GetInstanceId(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint(s.config.MetadataValues.InstanceId)
fmt.Fprintf(w, res)
server.Log.Info("GetInstanceId returning: ", res)
return
}

func (s *MetadataService) GetInstanceType(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint(s.config.MetadataValues.InstanceType)
fmt.Fprintf(w, res)
server.Log.Info("GetInstanceType returning: ", res)
return
}

func (s *MetadataService) GetIAM(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint("security-credentials/")
fmt.Fprintf(w, res)
server.Log.Info("GetIAM returning: ", res)
return
}

func (s *MetadataService) GetSecurityCredentials(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint(s.config.MetadataValues.SecurityCredentials.User)
server.Log.Info("GetSecurityCredentials returning: ", res)
fmt.Fprintf(w, res)
return
}

func (s *MetadataService) GetSecurityCredentialDetails(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
username := web.Vars(r)["username"]

status, res, err := j(r)
if username == s.config.MetadataValues.SecurityCredentials.User {
details, err := json.MarshalIndent(s.config.MetadataValues.SecurityCredentials, "", "\t")
if err != nil {
server.LogWithFields(r).WithFields(logrus.Fields{
"error": err,
}).Error("problems with serving request")
return http.StatusServiceUnavailable, nil, &jsonErr{"sorry, this service is unavailable"}
}
server.Log.Error("error converting security credentails to json: ", err)
http.Error(w, "", http.StatusNotFound)

server.LogWithFields(r).Info("success!")
return status, res, nil
return
}else {
server.Log.Info("GetSecurityCredentialDetails returning: ", details)

w.Write(details)
return
}
} else {
server.Log.Error("error, IAM user not found")
http.Error(w, "", http.StatusNotFound)
}

return
}

func (s *MetadataService) GetMetadataItem(r *http.Request) (int, interface{}, error) {
res := s.config.MetadataItems[r.URL.Path]
return http.StatusOK, res, nil
func (s *MetadataService) GetMetadataIndex(w http.ResponseWriter, r *http.Request) {

index := []string{"hostname",
"instance-id",
"instance-type",
"iam/"}
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
res := fmt.Sprint(strings.Join(index, "\n"))
server.Log.Info("GetMetadataIndex returning: ", res)
fmt.Fprintf(w, res )
return
}

func (s *MetadataService) GetIndex(r *http.Request) (int, interface{}, error) {
return http.StatusOK, "Mock EC2 Metadata Service", nil
func (s *MetadataService) GetIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Mock EC2 Metadata Service")
return
}

// JSONEndpoints is a listing of all endpoints available in the MetadataService.
func (s *MetadataService) Endpoints() map[string]map[string]http.HandlerFunc {
// Endpoints is a listing of all endpoints available in the MetadataService.
func (service *MetadataService) Endpoints() map[string]map[string]http.HandlerFunc {

handlers := make(map[string]map[string]http.HandlerFunc)
for url, value := range s.config.MetadataItems {
server.Log.Info("adding route for url", url, " value ", value)
for index, value := range service.config.MetadataPrefixes {
server.Log.Info("adding Metadata prefix (", index, ") ", value)

handlers[url] = map[string]http.HandlerFunc{
"GET": server.JSONToHTTP(s.GetMetadataItem).ServeHTTP,
handlers[value + "/" ] = map[string]http.HandlerFunc{
"GET": service.GetMetadataIndex,
}
handlers[value + "/hostname" ] = map[string]http.HandlerFunc{
"GET": service.GetHostName,
}
handlers[value + "/instance-id" ] = map[string]http.HandlerFunc{
"GET": service.GetInstanceId,
}
handlers[value + "/instance-type" ] = map[string]http.HandlerFunc{
"GET": service.GetInstanceType,
}
handlers[value + "/iam/" ] = map[string]http.HandlerFunc{
"GET": service.GetIAM,
}
handlers[value + "/iam/security-credentials/" ] = map[string]http.HandlerFunc{
"GET": service.GetSecurityCredentials,
}
handlers[value + "/iam/security-credentials/{username}" ] = map[string]http.HandlerFunc{
"GET": service.GetSecurityCredentialDetails,
}
}
handlers["/"] = map[string]http.HandlerFunc{
"GET": server.JSONToHTTP(s.GetIndex).ServeHTTP,
"GET": service.GetIndex,
}
return handlers
}
Expand All @@ -70,10 +166,10 @@ func (s *MetadataService) Prefix() string {
return "/"
}

type jsonErr struct {
Err string `json:"error"`
type error struct {
Err string
}

func (e *jsonErr) Error() string {
func (e *error) Error() string {
return e.Err
}
11 changes: 11 additions & 0 deletions systemd/mock-ec2-metadata.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Mock EC2 Metadata server
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/mock-ec2-metadata
Restart=always

[Install]
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package main
var GitCommit string

// The main version number that is being run at the moment.
const Version = "0.1.0"
const Version = "0.2.1"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
Expand Down

0 comments on commit a23e881

Please sign in to comment.