Commit 17ae8547 authored by Gurvinder Singh's avatar Gurvinder Singh
Browse files

initial commit for providing JWT token based on information from Dataporten

It adds user, groups and feide id information into token
parent 20ea5ec1
package conf
import (
"errors"
"github.com/spf13/viper"
)
func ReadConfig(filename string) error {
viper.SetConfigName(filename)
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
return errors.New(err.Error() + ("Fatal error in config file."))
}
return nil
}
func GetStringValue(key string) string {
return viper.GetString(key)
}
func GetBoolValue(key string) bool {
return viper.GetBool(key)
}
func GetIntValue(key string) int {
return viper.GetInt(key)
}
func GetStringArrayValue(key string) []string {
return viper.GetStringSlice(key)
}
func GetStringMapString(key string) map[string]string {
return viper.GetStringMapString(key)
}
hash: fbcc70bf29d6df51236f1ecd69b24562437431708eda5bb737ea503fd7927b9b
updated: 2017-03-01T13:10:48.926824594+01:00
imports:
- name: github.com/fsnotify/fsnotify
version: fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197
- name: github.com/hashicorp/hcl
version: eb6f65b2d77ed5078887f960ff570fbddbbeb49d
subpackages:
- hcl/ast
- hcl/parser
- hcl/scanner
- hcl/strconv
- hcl/token
- json/parser
- json/scanner
- json/token
- name: github.com/m4rw3r/uuid
version: 00c72d48d5aaaf3058e26d9641164c43ba239f33
- name: github.com/magiconair/properties
version: b3b15ef068fd0b17ddf408a23669f20811d194d2
- name: github.com/mitchellh/mapstructure
version: bfdb1a85537d60bc7e954e600c250219ea497417
- name: github.com/pelletier/go-buffruneio
version: df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d
- name: github.com/pelletier/go-toml
version: a1f048ba24490f9b0674a67e1ce995d685cddf4a
- name: github.com/SermoDigital/jose
version: 2bd9b81ac51d6d6134fcd4fd846bd2e7347a15f9
subpackages:
- crypto
- jws
- jwt
- name: github.com/Sirupsen/logrus
version: 0208149b40d863d2c1a2f8fe5753096a9cf2cc8b
- name: github.com/spf13/afero
version: 72b31426848c6ef12a7a8e216708cb0d1530f074
subpackages:
- mem
- name: github.com/spf13/cast
version: 56a7ecbeb18dde53c6db4bd96b541fd9741b8d44
- name: github.com/spf13/jwalterweatherman
version: fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66
- name: github.com/spf13/pflag
version: 5ccb023bc27df288a957c5e994cd44fd19619465
- name: github.com/spf13/viper
version: 5ed0fc31f7f453625df314d8e66b9791e8d13003
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- name: golang.org/x/net
version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages:
- html
- html/atom
- html/charset
- name: golang.org/x/oauth2
version: 3c3a985cb79f52a3190fbc056984415ca6763d01
- name: golang.org/x/sys
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
subpackages:
- unix
- name: golang.org/x/text
version: 2910a502d2bf9e43193af9d68ca516529614eed3
subpackages:
- encoding
- encoding/charmap
- encoding/htmlindex
- encoding/internal
- encoding/internal/identifier
- encoding/japanese
- encoding/korean
- encoding/simplifiedchinese
- encoding/traditionalchinese
- encoding/unicode
- internal/tag
- internal/utf8internal
- language
- runes
- transform
- unicode/norm
- name: gopkg.in/yaml.v2
version: 53feefa2559fb8dfa8d81baad31be332c97d6c77
- name: scm.uninett.no/laas/laasctl-auth
version: 2a6ec4dbe8b6086bc117b6fa5094abd84ff2d153
testImports:
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
package: github.com/uninett/jwt-tokenissuer
import:
- package: github.com/Sirupsen/logrus
version: ^0.11.4
- package: github.com/m4rw3r/uuid
version: ^1.0.0
- package: github.com/spf13/viper
- package: golang.org/x/oauth2
- package: github.com/SermoDigital/jose/jws
- package: scm.uninett.no/laas/laasctl-auth
- package: github.com/stretchr/testify/assert
- package: golang.org/x/net/html/charset
package main
import (
"crypto/rsa"
"fmt"
"io/ioutil"
"net/http"
"time"
auth "scm.uninett.no/laas/laasctl-auth"
"github.com/SermoDigital/jose/crypto"
"github.com/SermoDigital/jose/jws"
log "github.com/Sirupsen/logrus"
"github.com/uninett/jwt-tokenissuer/conf"
)
type JWTMiddleware struct {
RSAKey *rsa.PrivateKey
}
func newJWTMiddleWare(keyPath string) (*JWTMiddleware, error) {
keyBytes, err := ioutil.ReadFile(keyPath)
if err != nil {
log.Error("Failed in reading private key file ", err)
return nil, err
}
rsaKey, err := crypto.ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
log.Error("Failed in parsing private key file ", err)
return nil, err
}
return &JWTMiddleware{
RSAKey: rsaKey,
}, nil
}
func (jwm *JWTMiddleware) JWTTokenHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Fetch user and groups from context
user := r.Context().Value(auth.User).(string)
var principals []string
rawGroups := r.Context().Value(auth.Groups)
if rawGroups != nil {
if _, ok := rawGroups.([]string); ok {
principals = rawGroups.([]string)
}
}
// Get Client ID from request headers
clientID, clientIDFound := r.Header["X-Dataporten-Clientid"]
if !clientIDFound {
auth.ReturnError(w, r, "No audience", http.StatusBadRequest)
return
}
token := jwm.getJWTToken(w, r, user, principals, clientID[0])
if token == nil {
return
}
// We have the token, so send it as json
w.Header().Set("Content-Type", "text/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusCreated)
log.Debug("Issued token: ", fmt.Sprintf("%s", token))
fmt.Fprint(w, fmt.Sprintf("{\"token\": \"%s\"}", token))
return
})
}
func (jwm *JWTMiddleware) getJWTToken(
w http.ResponseWriter,
r *http.Request,
user string,
principals []string,
aud string) []byte {
if user == "" || aud == "" {
auth.ReturnError(w, r, "User or Audience is empty", http.StatusBadRequest)
log.Error("User or audience is empty")
return nil
}
now := time.Now()
claims := jws.Claims{}
// Set the claims
claims.SetSubject(user)
claims.SetIssuedAt(now)
claims.SetAudience(aud)
claims.Set("principals", principals)
claims.SetIssuer(conf.GetStringValue("engine.issuer_url"))
claims.SetExpiration(now.Add(time.Duration(conf.GetIntValue("engine.token_age")) * time.Second))
jwt := jws.NewJWT(claims, crypto.SigningMethodRS256)
token, err := jwt.Serialize(jwm.RSAKey)
if err != nil {
auth.ReturnError(w, r, "Failed in serializing token ", http.StatusInternalServerError)
log.Error("Failed in serializing token ", err)
return nil
}
return token
}
{
"proxy": {
"target": "https://httpbin.org"
},
"engine": {
"client_id": "00000-0000-000-000-00",
"client_secret": "00000-0000-000-000-00",
"issuer_url": "https://auth.dataporten.no",
"redirect_url": "http://localhost:8888/oauth2/callback",
"scopes": "userid,groups",
"signkey": "testtesttesttest",
"groups_endpoint": "https://groups-api.dataporten.no/groups/me/groups",
"token_type": "oauth2",
"twofactor": {
"all": false,
"principals": "",
"acr_values": "",
"backend": ""
},
"logging": {
"level": "debug"
}
},
"server": {
"port": 8888,
"health_port": 1337,
"cert": "cert.pem",
"key": "key.pem",
"readtimeout": 10,
"writetimeout": 20,
"idletimeout": 120,
"ssl": false,
"secure_cookie": false
}
}
package main
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/SermoDigital/jose/crypto"
"github.com/SermoDigital/jose/jws"
"github.com/SermoDigital/jose/jwt"
"github.com/stretchr/testify/assert"
"github.com/uninett/jwt-tokenissuer/conf"
auth "scm.uninett.no/laas/laasctl-auth"
)
type JWTToken struct {
Token string `json:"token"`
}
var jwm, _ = newJWTMiddleWare("./key.priv")
// Tests
func TestGetJWTToken(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", nil)
token := jwm.getJWTToken(w, r, "dummy", []string{"dummyPp"}, "dummyapp")
assert.NotNil(t, token)
token = jwm.getJWTToken(w, r, "dummy", nil, "dummyapp")
assert.NotNil(t, token)
token = jwm.getJWTToken(w, r, "dummy", nil, "")
assert.Nil(t, token)
assert.Equal(t, http.StatusBadRequest, w.Code)
token = jwm.getJWTToken(w, r, "", nil, "")
assert.Nil(t, token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestVerifyGoodToken(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", nil)
bytes, _ := ioutil.ReadFile("./key.pub")
rsaPublic, _ := crypto.ParseRSAPublicKeyFromPEM(bytes)
ctx := r.Context()
ctx = context.WithValue(ctx, auth.User, "dummy")
ctx = context.WithValue(ctx, auth.Groups, "dummyPp")
r = r.WithContext(ctx)
r.Header["X-Dataporten-Clientid"] = []string{"dummyapp"}
jwm.JWTTokenHandler().ServeHTTP(w, r)
assert.Equal(t, http.StatusCreated, w.Code)
body, err := ioutil.ReadAll(w.Body)
assert.Nil(t, err)
var jwtToken JWTToken
err = json.Unmarshal(body, &jwtToken)
assert.Nil(t, err)
parseJWT, err := jws.ParseJWT([]byte(jwtToken.Token))
assert.Nil(t, err)
claims := jwt.Claims{}
claims.SetAudience(r.Header.Get("X-Dataporten-Clientid"))
claims.SetIssuer(conf.GetStringValue("engine.issuer_url"))
var validator = &jwt.Validator{
Expected: claims,
EXP: 0,
NBF: 10 * time.Second,
Fn: nil,
}
err = parseJWT.Validate(rsaPublic, crypto.SigningMethodRS256, validator)
assert.Nil(t, err)
}
func TestVerifyBadToken(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", nil)
bytes, _ := ioutil.ReadFile("./key.pub")
rsaPublic, _ := crypto.ParseRSAPublicKeyFromPEM(bytes)
ctx := r.Context()
ctx = context.WithValue(ctx, auth.User, "dummy")
ctx = context.WithValue(ctx, auth.Groups, "dummyPp")
r = r.WithContext(ctx)
r.Header["X-Dataporten-Clientid"] = []string{"dummyapp"}
jwm.JWTTokenHandler().ServeHTTP(w, r)
assert.Equal(t, http.StatusCreated, w.Code)
body, err := ioutil.ReadAll(w.Body)
assert.Nil(t, err)
var jwtToken JWTToken
err = json.Unmarshal(body, &jwtToken)
assert.Nil(t, err)
parseJWT, err := jws.ParseJWT([]byte(jwtToken.Token))
assert.Nil(t, err)
claims := jwt.Claims{}
claims.SetAudience("invaliddummyapp")
claims.SetIssuer(conf.GetStringValue("engine.issuer_url"))
var validator = &jwt.Validator{
Expected: claims,
EXP: 0,
NBF: 10 * time.Second,
Fn: nil,
}
err = parseJWT.Validate(rsaPublic, crypto.SigningMethodRS256, validator)
assert.NotNil(t, err)
assert.Equal(t, "claim \"aud\" is invalid", err.Error())
}
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAzXexMzP4X3pJueI/Wgf593onU70Zf+yxhZdv5vIwTrj8TE9T
nGfPUBxnftvayNx8edXD6LaZO7XVaCkgydssTzBqqjqvtG3Hlx+5qTiOQkxmGWl6
oxaWZ2rBJrINFuKHUopQyC2ztkYkVzEJRCMDJzhFMBRGVlvfETaSUTLGbahWp6Mt
7EuKShrIWiEx/5emJUri1hTpQQXAE/WvS0rpDuribBLnidRd5lyT3/v9paPVzk8+
OYjDQ6ejfZr3cJY5par/sXctJ1tdvd72q2QVSLw5RL3+2YF+umu3l9wAhJ+kHR+Q
Oa78XxWy9+0eIsyxV+JqDTRs1qgzitSpc/qGkwIDAQABAoIBAGeQ72bJyWYG8+aY
Xip5XswZbH1PEe1jV0/pN2SHq0XjBHlKevCZeuv8hUZyd2NLkAqxb2B7ud1ZGEq6
gGZ+7WS1GO7ZSft+Yl6QhTsA4OQWMliYzFIsbn+OVfG1SdfBM0ito25javRzgpnY
g70ukMZSHL3RWmrpQBUjFrmLCG34xdR1I1aqoL17HA02Wu1x4M+/6fZlrqIVF3tY
S21I5FaT/Zv4h+9gGaVhjjSBwk5EVHagfgTnUlAElL9HjB7AKxsUTgZgx3WW7e9r
eoCX0YX1NnIQp97myfltbIzmrrRxai5b40Jc+NAVNAv0mEReEXIhk8/mv4Z49KXZ
6pvS8OECgYEA/kaN3ZZcjpdj+MyGfk27zgUNakVcoEmcVpcpXRjMIegNB6k2G/aK
PJmmYZVAq46SgXc+I+VoeJAVVnenIepDw7eoapjXP+nuh+7hUImgPcep2+CZRHgI
w7yfJXJDnN2/Ynb6GCMDKgzWrzGzTE7KsFl2Lb+v2bpm3oyNzBOVeGsCgYEAztxn
GsWi6vVIxSqLvn/OP91m6wJFBm4nM/MuwBrvGnrC2n98h9BLSNuK+PHIPZ2QjnxR
xqNBK/DRPLQlQAhjlp+Sw0NRmFkaichIwjNKxtwbtSss6YBvEVH23kq0zWThKoKv
Bv00SGLInZBQkhqC0axcuKgjIr9RgSTpcpbU1HkCgYAx3D3nTJu7rj4BRIG+9Jk2
DdGSnCVMnQKWDDAlPD6TC4C6ltiwZQi4V1zD19OFkIBz+KDrwwlYUtCMUwwn31sI
XtQcivnyY3nNl+8AruwIEUXigskrHuqBD1SZsC1H7kegFDp4Ty1IqpnSYsmI1g5y
xCgjUmo2JJIVY7ROZyyxHwKBgHoSEj34NwXULVc8ni6SmRGTA7LXKgh7ZGMBtKrk
ugi0PNxXsxMqVeLuu3ngpDvICZAryM4hdMoLWE4sOHcq/CncjOzxgusWWdTI/jar
sgrbxPYKPnykr+ZWqgnPqBY027MMX2EOdpXoxV5F1WYCgwRcZHMLtYBBykUdlS1r
2LhxAoGACeIIK8rPpKjucnDeEOXtwFIspQM54vsvPQC7VMc80SQHOtaq7USuFXnX
n5L410IaaQl58LfIsxhUegELzamiPx7WxtDJ1OacQGgP9FVRFbYUfKFiSPCjbspe
Bxa2eiwCthUMPZCCUtgmPgpgiaCj4zYaXIZ1ZD438r1VNcCQYw4=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzXexMzP4X3pJueI/Wgf5
93onU70Zf+yxhZdv5vIwTrj8TE9TnGfPUBxnftvayNx8edXD6LaZO7XVaCkgydss
TzBqqjqvtG3Hlx+5qTiOQkxmGWl6oxaWZ2rBJrINFuKHUopQyC2ztkYkVzEJRCMD
JzhFMBRGVlvfETaSUTLGbahWp6Mt7EuKShrIWiEx/5emJUri1hTpQQXAE/WvS0rp
DuribBLnidRd5lyT3/v9paPVzk8+OYjDQ6ejfZr3cJY5par/sXctJ1tdvd72q2QV
SLw5RL3+2YF+umu3l9wAhJ+kHR+QOa78XxWy9+0eIsyxV+JqDTRs1qgzitSpc/qG
kwIDAQAB
-----END PUBLIC KEY-----
package main
import (
"crypto/tls"
"flag"
"fmt"
"net"
"net/http"
"os"
"time"
log "github.com/Sirupsen/logrus"
"github.com/uninett/jwt-tokenissuer/conf"
auth "scm.uninett.no/laas/laasctl-auth"
)
var version = "none"
var startTime time.Time
func init() {
// Log as JSON to stderr
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stderr)
// Find config file
err := conf.ReadConfig("jwt")
if err != nil {
log.WithFields(log.Fields{
"detail": err,
}).Fatal("Could not read configuration")
}
// Set up correct log level
lvl, err := log.ParseLevel(conf.GetStringValue("engine.logging.level"))
if err != nil {
log.WithFields(log.Fields{
"detail": err,
}).Warn("Could not parse log level, using default")
log.SetLevel(log.WarnLevel)
} else {
log.SetLevel(lvl)
}
}
func listenHTTP(ssl bool, port int) {
srv := &http.Server{
ReadTimeout: time.Duration(conf.GetIntValue("server.readtimeout")) * time.Second,
WriteTimeout: time.Duration(conf.GetIntValue("server.writetimeout")) * time.Second,
IdleTimeout: time.Duration(conf.GetIntValue("server.idletimeout")) * time.Second,
Addr: fmt.Sprintf(":%d", port),
Handler: nil,
}
if ssl {
// Taken from https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/
tlsConfig := &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks. Does nothing on clients.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519, // Go 1.8 only
},
}
srv.TLSConfig = tlsConfig
log.Fatal(srv.ListenAndServeTLS(
conf.GetStringValue("server.cert"),
conf.GetStringValue("server.key")))
} else {
log.Fatal(srv.ListenAndServe())
}
}
func main() {
// Set up a version option, until we get a /healthz endpoint or
// something similar
showVersion := flag.Bool("version", false, "Prints version information and exits")
flag.Parse()
if *showVersion {
fmt.Println("oidc-tokenissuer version", version)
os.Exit(0)
}
// Initialize Authentication middleware
auth.SetConfig(
[]string{"dataporten"},
nil,
conf.GetStringMapString("engine.dataporten.basic_auth"),
conf.GetStringValue("engine.dataporten.groups_endpoint"),
)
// Initialize the JWTTokenMiddleware
jwm, err := newJWTMiddleWare(conf.GetStringValue("engine.private_key"))
if err != nil {
log.Error("Failed in initializing JWT Middleware ", err)
os.Exit(1)
}
// Configure routes
http.Handle("/healthz", healthzHandler())
http.Handle("/", auth.MiddlewareHandler(jwm.JWTTokenHandler()))
startTime = time.Now()
port := conf.GetIntValue("server.port")
ssl := conf.GetBoolValue("server.ssl")
go listenHTTP(ssl, port)
// Start TCP server for health checks
healthPort := conf.GetIntValue("server.health_port")
server, err := net.Listen("tcp", fmt.Sprintf(":%d", healthPort))
if server == nil {
panic("couldn't set up tcp socket: " + err.Error())
}
log.Info("JWT Tokenissuer has been started and listening on ", port)
conns := clientTCPConns(server)
for {
go handleTCPConn(<-conns)
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment