Add supprt for new Rails 5.2 aes-256-gcm cookies
This commit is contained in:
parent
96adec81bd
commit
512647156e
@ -35,7 +35,7 @@ RUN apk add --no-cache tzdata
|
||||
|
||||
COPY --from=go-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=go-build /app/service .
|
||||
COPY --from=go-build /app/*.yml ./
|
||||
COPY --from=go-build /app/config/*.yml ./
|
||||
|
||||
RUN chmod +x /app/service
|
||||
USER nobody
|
||||
|
@ -2,7 +2,7 @@ title: Super Graph Development
|
||||
host_port: 0.0.0.0:8080
|
||||
web_ui: true
|
||||
debug_level: 1
|
||||
enable_tracing: false
|
||||
enable_tracing: true
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
# valid values: always, per_query, never
|
||||
@ -25,21 +25,32 @@ auth_fail_block: never
|
||||
# sheep: sheep
|
||||
|
||||
auth:
|
||||
type: header
|
||||
# Can be 'rails' or 'jwt'
|
||||
type: rails
|
||||
cookie: _app_session
|
||||
|
||||
# Comment this out if you want to disable setting
|
||||
# the user_id via a header. Good for testing
|
||||
header: X-User-ID
|
||||
|
||||
# rails_cookie:
|
||||
# secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9...
|
||||
rails:
|
||||
# Rails version this is used for reading the
|
||||
# various cookies formats.
|
||||
version: 5.2
|
||||
|
||||
# rails_memcache:
|
||||
# host: 127.0.0.1
|
||||
# Found in 'Rails.application.config.secret_key_base'
|
||||
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
|
||||
|
||||
# Remote cookie store. (memcache or redis)
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: test
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
|
||||
# rails_redis:
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: ""
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
# In most cases you don't need these
|
||||
# salt: "encrypted cookie"
|
||||
# sign_salt: "signed encrypted cookie"
|
||||
# auth_salt: "authenticated encrypted cookie"
|
||||
|
||||
# jwt:
|
||||
# provider: auth0
|
||||
@ -47,6 +58,7 @@ auth:
|
||||
# public_key_file: /secrets/public_key.pem
|
||||
# public_key_type: ecdsa #rsa
|
||||
|
||||
|
||||
database:
|
||||
type: postgres
|
||||
host: db
|
||||
@ -77,18 +89,24 @@ database:
|
||||
|
||||
fields:
|
||||
- name: users
|
||||
# This filter will overwrite defaults.filter
|
||||
filter: ["{ id: { eq: $user_id } }"]
|
||||
|
||||
- name: products
|
||||
# Multiple filters are AND'd together
|
||||
filter: [
|
||||
"{ price: { gt: 0 } }",
|
||||
"{ price: { lt: 8 } }"
|
||||
]
|
||||
|
||||
- name: customers
|
||||
# No filter is used for this field not
|
||||
# even defaults.filter
|
||||
filter: none
|
||||
|
||||
- name: me
|
||||
- # You can create new fields that have a
|
||||
# real db table backing them
|
||||
name: me
|
||||
table: users
|
||||
filter: ["{ id: { eq: $user_id } }"]
|
||||
|
113
config/prod.yml
Normal file
113
config/prod.yml
Normal file
@ -0,0 +1,113 @@
|
||||
title: Super Graph Production
|
||||
host_port: 0.0.0.0:8080
|
||||
web_ui: false
|
||||
debug_level: 0
|
||||
enable_tracing: false
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
# valid values: always, per_query, never
|
||||
auth_fail_block: always
|
||||
|
||||
# Postgres related environment Variables
|
||||
# SG_DATABASE_HOST
|
||||
# SG_DATABASE_PORT
|
||||
# SG_DATABASE_USER
|
||||
# SG_DATABASE_PASSWORD
|
||||
|
||||
# Auth related environment Variables
|
||||
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
|
||||
# SG_AUTH_RAILS_REDIS_URL
|
||||
# SG_AUTH_RAILS_REDIS_PASSWORD
|
||||
# SG_AUTH_JWT_PUBLIC_KEY_FILE
|
||||
|
||||
# inflections:
|
||||
# person: people
|
||||
# sheep: sheep
|
||||
|
||||
auth:
|
||||
# Can be 'rails' or 'jwt'
|
||||
type: rails
|
||||
cookie: _app_session
|
||||
|
||||
# Comment this out if you want to disable setting
|
||||
# the user_id via a header. Good for testing
|
||||
header: X-User-ID
|
||||
|
||||
rails:
|
||||
# Rails version this is used for reading the
|
||||
# various cookies formats.
|
||||
version: 5.2
|
||||
|
||||
# Found in 'Rails.application.config.secret_key_base'
|
||||
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
|
||||
|
||||
# Remote cookie store. (memcache or redis)
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: test
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
|
||||
# In most cases you don't need these
|
||||
# salt: "encrypted cookie"
|
||||
# sign_salt: "signed encrypted cookie"
|
||||
# auth_salt: "authenticated encrypted cookie"
|
||||
|
||||
# jwt:
|
||||
# provider: auth0
|
||||
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
|
||||
# public_key_file: /secrets/public_key.pem
|
||||
# public_key_type: ecdsa #rsa
|
||||
|
||||
database:
|
||||
type: postgres
|
||||
host: db
|
||||
port: 5432
|
||||
dbname: app_development
|
||||
user: postgres
|
||||
password: ''
|
||||
#pool_size: 10
|
||||
#max_retries: 0
|
||||
#log_level: "debug"
|
||||
|
||||
# Define variables here that you want to use in filters
|
||||
variables:
|
||||
account_id: "select account_id from users where id = $user_id"
|
||||
|
||||
# Define defaults to for the field key and values below
|
||||
defaults:
|
||||
filter: ["{ user_id: { eq: $user_id } }"]
|
||||
|
||||
# Fields and table names that you wish to block
|
||||
blacklist:
|
||||
- ar_internal_metadata
|
||||
- schema_migrations
|
||||
- secret
|
||||
- password
|
||||
- encrypted
|
||||
- token
|
||||
|
||||
fields:
|
||||
- name: users
|
||||
# This filter will overwrite defaults.filter
|
||||
filter: ["{ id: { eq: $user_id } }"]
|
||||
|
||||
- name: products
|
||||
# Multiple filters are AND'd together
|
||||
filter: [
|
||||
"{ price: { gt: 0 } }",
|
||||
"{ price: { lt: 8 } }"
|
||||
]
|
||||
|
||||
- name: customers
|
||||
# No filter is used for this field not
|
||||
# even defaults.filter
|
||||
filter: none
|
||||
|
||||
- # You can create new fields that have a
|
||||
# real db table backing them
|
||||
name: me
|
||||
table: users
|
||||
filter: ["{ id: { eq: $user_id } }"]
|
||||
|
||||
# - name: posts
|
||||
# filter: ["{ account_id: { _eq: $account_id } }"]
|
@ -3,6 +3,13 @@ services:
|
||||
db:
|
||||
image: postgres
|
||||
|
||||
# nginx:
|
||||
# image: nginx:latest
|
||||
# volumes:
|
||||
# - ./example/nginx.conf:/etc/nginx/nginx.conf
|
||||
# ports:
|
||||
# - 3001:3001
|
||||
|
||||
super_graph:
|
||||
build:
|
||||
context: .
|
||||
@ -27,4 +34,5 @@ services:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
- super_graph
|
||||
- super_graph
|
||||
# - nginx
|
@ -423,21 +423,32 @@ auth_fail_block: never
|
||||
# sheep: sheep
|
||||
|
||||
auth:
|
||||
type: header
|
||||
# Can be 'rails' or 'jwt'
|
||||
type: rails
|
||||
cookie: _app_session
|
||||
|
||||
# Comment this out if you want to disable setting
|
||||
# the user_id via a header. Good for testing
|
||||
header: X-User-ID
|
||||
|
||||
# rails_cookie:
|
||||
# secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9...
|
||||
rails:
|
||||
# Rails version this is used for reading the
|
||||
# various cookies formats.
|
||||
version: 5.2
|
||||
|
||||
# rails_memcache:
|
||||
# host: 127.0.0.1
|
||||
# Found in 'Rails.application.config.secret_key_base'
|
||||
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
|
||||
|
||||
# Remote cookie store. (memcache or redis)
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: test
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
|
||||
# rails_redis:
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: ""
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
# In most cases you don't need these
|
||||
# salt: "encrypted cookie"
|
||||
# sign_salt: "signed encrypted cookie"
|
||||
# auth_salt: "authenticated encrypted cookie"
|
||||
|
||||
# jwt:
|
||||
# provider: auth0
|
||||
@ -452,9 +463,9 @@ database:
|
||||
dbname: app_development
|
||||
user: postgres
|
||||
password: ''
|
||||
#pool_size: 10
|
||||
#max_retries: 0
|
||||
#log_level: "debug"
|
||||
# pool_size: 10
|
||||
# max_retries: 0
|
||||
# log_level: "debug"
|
||||
|
||||
# Define variables here that you want to use in filters
|
||||
variables:
|
||||
|
@ -8,7 +8,7 @@ Devise.setup do |config|
|
||||
# confirmation, reset password and unlock tokens in the database.
|
||||
# Devise will use the `secret_key_base` as its `secret_key`
|
||||
# by default. You can change it below and use your own secret key.
|
||||
# config.secret_key = 'da45f01cc03f113df2ac8788aab0833ff0ce6d3f2e298e964e0b35c7f63877588054a503106f0102d412987a45ed49e9a49697a6cf66da22f04ca6dafd24f76d'
|
||||
# config.secret_key = ''
|
||||
|
||||
# ==> Controller configuration
|
||||
# Configure the parent class to the devise controllers.
|
||||
|
1
example/config/initializers/secret_tokens.rb
Normal file
1
example/config/initializers/secret_tokens.rb
Normal file
@ -0,0 +1 @@
|
||||
Rails.application.config.secret_key_base= '0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566'
|
25
example/nginx.conf
Normal file
25
example/nginx.conf
Normal file
@ -0,0 +1,25 @@
|
||||
events { }
|
||||
|
||||
http {
|
||||
server {
|
||||
server_name localhost;
|
||||
listen 3001;
|
||||
|
||||
location /api/v1/graphql {
|
||||
proxy_pass http://super_graph:8080;
|
||||
proxy_redirect off;
|
||||
#rewrite ^/api/v1/graphql$ $1 break;
|
||||
}
|
||||
|
||||
location /super-graph {
|
||||
proxy_pass http://super_graph:8080;
|
||||
proxy_redirect off;
|
||||
rewrite ^/super-graph/(.*)$ /$1 break;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://web:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
}
|
2
go.mod
2
go.mod
@ -1,6 +1,7 @@
|
||||
module github.com/dosco/super-graph
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
@ -13,6 +14,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.4.0
|
||||
github.com/spf13/viper v1.3.1
|
||||
github.com/valyala/fasttemplate v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a
|
||||
mellium.im/sasl v0.2.1 // indirect
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 h1:+qz9Ga6l6lKw6fgvk5RMV5HQznSLvI8Zxajwdj4FhFg=
|
||||
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
|
79
prod.yml
79
prod.yml
@ -1,79 +0,0 @@
|
||||
title: Super Graph Production
|
||||
host_port: 0.0.0.0:8080
|
||||
web_ui: false
|
||||
debug_level: 0
|
||||
enable_tracing: false
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
# valid values: always, per_query, never
|
||||
auth_fail_block: always
|
||||
|
||||
# Postgres related environment Variables
|
||||
# SG_DATABASE_HOST
|
||||
# SG_DATABASE_PORT
|
||||
# SG_DATABASE_USER
|
||||
# SG_DATABASE_PASSWORD
|
||||
|
||||
# Auth related environment Variables
|
||||
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
|
||||
# SG_AUTH_RAILS_REDIS_URL
|
||||
# SG_AUTH_RAILS_REDIS_PASSWORD
|
||||
# SG_AUTH_JWT_PUBLIC_KEY_FILE
|
||||
|
||||
# inflections:
|
||||
# person: people
|
||||
# sheep: sheep
|
||||
|
||||
auth:
|
||||
type: cookie
|
||||
cookie: _app_session
|
||||
|
||||
rails_cookie:
|
||||
secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9...
|
||||
|
||||
# rails_memcache:
|
||||
# host: 127.0.0.1
|
||||
|
||||
# rails_redis:
|
||||
# url: redis://127.0.0.1:6379
|
||||
# password: ""
|
||||
# max_idle: 80,
|
||||
# max_active: 12000,
|
||||
|
||||
# jwt:
|
||||
# provider: auth0
|
||||
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
|
||||
# public_key_file: /secrets/public_key.pem
|
||||
# public_key_type: ecdsa #rsa
|
||||
|
||||
database:
|
||||
type: postgres
|
||||
host: db
|
||||
port: 5432
|
||||
dbname: app_development
|
||||
user: postgres
|
||||
password: ''
|
||||
#pool_size: 10
|
||||
#max_retries: 0
|
||||
#log_level: "debug"
|
||||
|
||||
# Define variables here that you want to use in filters
|
||||
variables:
|
||||
account_id: "select account_id from users where id = $user_id"
|
||||
|
||||
# Define defaults to for the field key and values below
|
||||
defaults:
|
||||
filter: ["{ id: { _eq: $user_id } }"]
|
||||
|
||||
# Fields and table names that you wish to block
|
||||
blacklist:
|
||||
- ar_internal_metadata
|
||||
- schema_migrations
|
||||
- secret
|
||||
- password
|
||||
- encrypted
|
||||
- token
|
||||
|
||||
fields:
|
||||
- name: users
|
||||
filter: ["{ id: { _eq: $user_id } }"]
|
171
rails/auth.go
Normal file
171
rails/auth.go
Normal file
@ -0,0 +1,171 @@
|
||||
package rails
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/adjust/gorails/marshal"
|
||||
)
|
||||
|
||||
const (
|
||||
salt = "encrypted cookie"
|
||||
signSalt = "signed encrypted cookie"
|
||||
authSalt = "authenticated encrypted cookie"
|
||||
railsCipher = "aes-256-cbc"
|
||||
railsCipher52 = "aes-256-gcm"
|
||||
)
|
||||
|
||||
var (
|
||||
errSessionData = errors.New("error decoding session data")
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Cipher string
|
||||
Secret string
|
||||
Salt string
|
||||
SignSalt string
|
||||
AuthSalt string
|
||||
}
|
||||
|
||||
func NewAuth(version, secret string) (*Auth, error) {
|
||||
ra := &Auth{
|
||||
Secret: secret,
|
||||
Salt: salt,
|
||||
SignSalt: signSalt,
|
||||
AuthSalt: authSalt,
|
||||
}
|
||||
|
||||
ver, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rails auth: %s", err)
|
||||
}
|
||||
|
||||
gt52, err := semver.NewConstraint(">= 5.2")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rails auth: %s", err)
|
||||
}
|
||||
|
||||
if gt52.Check(ver) {
|
||||
ra.Cipher = railsCipher52
|
||||
} else {
|
||||
ra.Cipher = railsCipher
|
||||
}
|
||||
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
func (ra *Auth) ParseCookie(cookie string) (userID string, err error) {
|
||||
var dcookie []byte
|
||||
|
||||
switch ra.Cipher {
|
||||
case railsCipher:
|
||||
dcookie, err = parseCookie(cookie, ra.Secret, ra.Salt, ra.SignSalt)
|
||||
|
||||
case railsCipher52:
|
||||
dcookie, err = parseCookie52(cookie, ra.Secret, ra.AuthSalt)
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unknown rails cookie cipher '%s'", ra.Cipher)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dcookie[0] != '{' {
|
||||
userID, err = getUserId4(dcookie)
|
||||
} else {
|
||||
userID, err = getUserId(dcookie)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseCookie(cookie string) (string, error) {
|
||||
if cookie[0] != '{' {
|
||||
return getUserId4([]byte(cookie))
|
||||
}
|
||||
|
||||
return getUserId([]byte(cookie))
|
||||
}
|
||||
|
||||
func getUserId(data []byte) (userID string, err error) {
|
||||
var sessionData map[string]interface{}
|
||||
|
||||
err = json.Unmarshal(data, &sessionData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userKey, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errors.New("key 'warden.user.user.key' not found in session data")
|
||||
}
|
||||
|
||||
items, ok := userKey.([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uids, ok := items[0].([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, ok := uids[0].(float64)
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", int64(uid))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserId4(data []byte) (userID string, err error) {
|
||||
sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wardenData, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
wardenUserKey, err := wardenData.GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(wardenUserKey) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
userData, err := wardenUserKey[0].GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(userData) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := userData[0].GetAsInteger()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", uid)
|
||||
|
||||
return
|
||||
}
|
@ -1,15 +1,22 @@
|
||||
package serv
|
||||
package rails
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRailsEncryptedSession(t *testing.T) {
|
||||
func TestRailsEncryptedSession1(t *testing.T) {
|
||||
cookie := "dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6"
|
||||
|
||||
secret := "development_secret"
|
||||
|
||||
userID, err := railsAuth(cookie, secret)
|
||||
ra := Auth{
|
||||
Cipher: railsCipher,
|
||||
Secret: secret,
|
||||
Salt: salt,
|
||||
SignSalt: signSalt,
|
||||
}
|
||||
|
||||
userID, err := ra.ParseCookie(cookie)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@ -20,6 +27,29 @@ func TestRailsEncryptedSession(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailsEncryptedSession52(t *testing.T) {
|
||||
cookie :=
|
||||
"fZy1lt%2FIuXh2cpQgy3wWjbvabh1AqJX%2Bt6qO4D95DOZIpDhMyK2HqPFeNoaBtrXCUa9%2BDQuvbs1GX6tuccEAp14QPLNhm0PPJS5U1pRHqPLWaqT%2BBPYP%2BY9bo677komm9CPuOCOqBKf7rv3%2F4ptLmVO7iefB%2FP2ZlkV1848Johv5q%2B5PGyMxII2BEQnBdS3Petw6lRu741Bquc8z9VofC3t4%2F%2BLxVz%2BvBbTg--VL0MorYITXB8Dj3W--0yr0sr6pRU%2FwlYMQ%2BpEifA%3D%3D"
|
||||
|
||||
secret := "0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566"
|
||||
|
||||
ra := Auth{
|
||||
Cipher: railsCipher52,
|
||||
Secret: secret,
|
||||
AuthSalt: authSalt,
|
||||
}
|
||||
|
||||
userID, err := ra.ParseCookie(cookie)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID != "2" {
|
||||
t.Errorf("Expecting userID 2 got %s", userID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailsJsonSession(t *testing.T) {
|
||||
sessionData := `{"warden.user.user.key":[[1],"secret"]}`
|
||||
|
62
rails/cookie.go
Normal file
62
rails/cookie.go
Normal file
@ -0,0 +1,62 @@
|
||||
package rails
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/adjust/gorails/session"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
func parseCookie(cookie, secretKeyBase, salt, signSalt string) ([]byte, error) {
|
||||
return session.DecryptSignedCookie(
|
||||
cookie,
|
||||
secretKeyBase,
|
||||
salt,
|
||||
signSalt)
|
||||
}
|
||||
|
||||
// {"session_id":"a71d6ffcd4ed5572ea2097f569eb95ef","warden.user.user.key":[[2],"$2a$11$q9Br7m4wJxQvF11hAHvTZO"],"_csrf_token":"HsYgrD2YBaWAabOYceN0hluNRnGuz49XiplmMPt43aY="}
|
||||
|
||||
func parseCookie52(cookie, secretKeyBase, authSalt string) ([]byte, error) {
|
||||
ecookie, err := url.QueryUnescape(cookie)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vectors := strings.Split(ecookie, "--")
|
||||
|
||||
body, err := base64.RawStdEncoding.DecodeString(vectors[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iv, err := base64.RawStdEncoding.DecodeString(vectors[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := base64.StdEncoding.DecodeString(vectors[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := pbkdf2.Key([]byte(secretKeyBase), []byte(authSalt),
|
||||
1000, 32, sha1.New)
|
||||
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Open(nil, iv, append(body, tag...), nil)
|
||||
}
|
51
serv/auth.go
51
serv/auth.go
@ -2,62 +2,47 @@ package serv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
salt = "encrypted cookie"
|
||||
signSalt = "signed encrypted cookie"
|
||||
emptySecret = ""
|
||||
authHeader = "Authorization"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
userIDProviderKey = struct{}{}
|
||||
userIDKey = struct{}{}
|
||||
errSessionData = errors.New("error decoding session data")
|
||||
)
|
||||
|
||||
func headerHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
fn := conf.Auth.Header
|
||||
if len(fn) == 0 {
|
||||
panic(errors.New("no auth.header defined"))
|
||||
func headerAuth(r *http.Request, c *config) *http.Request {
|
||||
if len(c.Auth.Header) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Header.Get(fn)
|
||||
if len(userID) == 0 {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userID := r.Header.Get(c.Auth.Header)
|
||||
if len(userID) != 0 {
|
||||
ctx := context.WithValue(r.Context(), userIDKey, userID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func withAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
at := conf.Auth.Type
|
||||
ru := conf.Auth.Rails.URL
|
||||
|
||||
switch at {
|
||||
case "header":
|
||||
return headerHandler(next)
|
||||
case "rails":
|
||||
if strings.HasPrefix(ru, "memcache:") {
|
||||
return railsMemcacheHandler(next)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ru, "redis:") {
|
||||
return railsRedisHandler(next)
|
||||
}
|
||||
|
||||
case "rails_cookie":
|
||||
return railsCookieHandler(next)
|
||||
|
||||
case "rails_memcache":
|
||||
return railsMemcacheHandler(next)
|
||||
|
||||
case "rails_redis":
|
||||
return railsRedisHandler(next)
|
||||
|
||||
case "jwt":
|
||||
return jwtHandler(next)
|
||||
|
||||
default:
|
||||
return next
|
||||
}
|
||||
|
||||
return next
|
||||
|
@ -10,7 +10,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
jwtBase int = iota
|
||||
authHeader = "Authorization"
|
||||
jwtBase int = iota
|
||||
jwtAuth0
|
||||
)
|
||||
|
||||
@ -57,6 +58,11 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var tok string
|
||||
|
||||
if rn := headerAuth(r, conf); rn != nil {
|
||||
next.ServeHTTP(w, rn)
|
||||
return
|
||||
}
|
||||
|
||||
if len(cookie) != 0 {
|
||||
ck, err := r.Cookie(cookie)
|
||||
if err != nil {
|
||||
|
@ -2,14 +2,14 @@ package serv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/adjust/gorails/marshal"
|
||||
"github.com/adjust/gorails/session"
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
"github.com/dosco/super-graph/rails"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
@ -19,21 +19,20 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
panic(errors.New("no auth.cookie defined"))
|
||||
}
|
||||
|
||||
authURL := conf.Auth.RailsRedis.URL
|
||||
if len(authURL) == 0 {
|
||||
panic(errors.New("no auth.rails_redis.url defined"))
|
||||
if len(conf.Auth.Rails.URL) == 0 {
|
||||
log.Fatal(errors.New("no auth.rails.url defined"))
|
||||
}
|
||||
|
||||
rp := &redis.Pool{
|
||||
MaxIdle: conf.Auth.RailsRedis.MaxIdle,
|
||||
MaxActive: conf.Auth.RailsRedis.MaxActive,
|
||||
MaxIdle: conf.Auth.Rails.MaxIdle,
|
||||
MaxActive: conf.Auth.Rails.MaxActive,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.DialURL(authURL)
|
||||
c, err := redis.DialURL(conf.Auth.Rails.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pwd := conf.Auth.RailsRedis.Password
|
||||
pwd := conf.Auth.Rails.Password
|
||||
if len(pwd) != 0 {
|
||||
if _, err := c.Do("AUTH", pwd); err != nil {
|
||||
panic(err)
|
||||
@ -44,6 +43,11 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if rn := headerAuth(r, conf); rn != nil {
|
||||
next.ServeHTTP(w, rn)
|
||||
return
|
||||
}
|
||||
|
||||
ck, err := r.Cookie(cookie)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
@ -57,7 +61,7 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := railsAuth(string(sessionData), emptySecret)
|
||||
userID, err := rails.ParseCookie(string(sessionData))
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
@ -74,14 +78,23 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
panic(errors.New("no auth.cookie defined"))
|
||||
}
|
||||
|
||||
host := conf.Auth.RailsMemcache.Host
|
||||
if len(host) == 0 {
|
||||
panic(errors.New("no auth.rails_memcache.host defined"))
|
||||
if len(conf.Auth.Rails.URL) == 0 {
|
||||
log.Fatal(errors.New("no auth.rails.url defined"))
|
||||
}
|
||||
|
||||
mc := memcache.New(host)
|
||||
rURL, err := url.Parse(conf.Auth.Rails.URL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
mc := memcache.New(rURL.Host)
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if rn := headerAuth(r, conf); rn != nil {
|
||||
next.ServeHTTP(w, rn)
|
||||
return
|
||||
}
|
||||
|
||||
ck, err := r.Cookie(cookie)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
@ -95,7 +108,7 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := railsAuth(string(item.Value), emptySecret)
|
||||
userID, err := rails.ParseCookie(string(item.Value))
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
@ -112,11 +125,17 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
panic(errors.New("no auth.cookie defined"))
|
||||
}
|
||||
|
||||
secret := conf.Auth.RailsCookie.SecretKeyBase
|
||||
if len(secret) == 0 {
|
||||
panic(errors.New("no auth.rails_cookie.secret_key_base defined"))
|
||||
ra, err := railsAuth(conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if rn := headerAuth(r, conf); rn != nil {
|
||||
next.ServeHTTP(w, rn)
|
||||
return
|
||||
}
|
||||
|
||||
ck, err := r.Cookie(cookie)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
@ -124,7 +143,7 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := railsAuth(ck.Value, secret)
|
||||
userID, err := ra.ParseCookie(ck.Value)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
next.ServeHTTP(w, r)
|
||||
@ -136,98 +155,33 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func railsAuth(cookie, secret string) (userID string, err error) {
|
||||
var dcookie []byte
|
||||
func railsAuth(c *config) (*rails.Auth, error) {
|
||||
secret := c.Auth.Rails.SecretKeyBase
|
||||
if len(secret) == 0 {
|
||||
return nil, errors.New("no auth.rails.secret_key_base defined")
|
||||
}
|
||||
|
||||
dcookie, err = session.DecryptSignedCookie(cookie, secret, salt, signSalt)
|
||||
version := c.Auth.Rails.Version
|
||||
if len(version) == 0 {
|
||||
return nil, errors.New("no auth.rails.version defined")
|
||||
}
|
||||
|
||||
ra, err := rails.NewAuth(version, secret)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dcookie[0] != '{' {
|
||||
userID, err = getUserId4(dcookie)
|
||||
} else {
|
||||
userID, err = getUserId(dcookie)
|
||||
if len(c.Auth.Rails.Salt) != 0 {
|
||||
ra.Salt = c.Auth.Rails.Salt
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserId(data []byte) (userID string, err error) {
|
||||
var sessionData map[string]interface{}
|
||||
|
||||
err = json.Unmarshal(data, &sessionData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userKey, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errors.New("key 'warden.user.user.key' not found in session data")
|
||||
}
|
||||
|
||||
items, ok := userKey.([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uids, ok := items[0].([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, ok := uids[0].(float64)
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", int64(uid))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserId4(data []byte) (userID string, err error) {
|
||||
sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wardenData, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
wardenUserKey, err := wardenData.GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(wardenUserKey) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
userData, err := wardenUserKey[0].GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(userData) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := userData[0].GetAsInteger()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", uid)
|
||||
|
||||
return
|
||||
if len(conf.Auth.Rails.SignSalt) != 0 {
|
||||
ra.SignSalt = c.Auth.Rails.SignSalt
|
||||
}
|
||||
|
||||
if len(conf.Auth.Rails.AuthSalt) != 0 {
|
||||
ra.AuthSalt = c.Auth.Rails.AuthSalt
|
||||
}
|
||||
|
||||
return ra, nil
|
||||
}
|
||||
|
23
serv/serv.go
23
serv/serv.go
@ -48,20 +48,17 @@ type config struct {
|
||||
Cookie string
|
||||
Header string
|
||||
|
||||
RailsCookie struct {
|
||||
Rails struct {
|
||||
Version string
|
||||
SecretKeyBase string `mapstructure:"secret_key_base"`
|
||||
} `mapstructure:"rails_cookie"`
|
||||
|
||||
RailsMemcache struct {
|
||||
Host string
|
||||
} `mapstructure:"rails_memcache"`
|
||||
|
||||
RailsRedis struct {
|
||||
URL string
|
||||
Password string
|
||||
MaxIdle int `mapstructure:"max_idle"`
|
||||
MaxActive int `mapstructure:"max_active"`
|
||||
} `mapstructure:"rails_redis"`
|
||||
URL string
|
||||
Password string
|
||||
MaxIdle int `mapstructure:"max_idle"`
|
||||
MaxActive int `mapstructure:"max_active"`
|
||||
Salt string
|
||||
SignSalt string `mapstructure:"sign_salt"`
|
||||
AuthSalt string `mapstructure:"auth_salt"`
|
||||
}
|
||||
|
||||
JWT struct {
|
||||
Provider string
|
||||
|
Loading…
Reference in New Issue
Block a user