572 lines
17 KiB
Go
572 lines
17 KiB
Go
|
package chef
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/rsa"
|
||
|
"crypto/x509"
|
||
|
"encoding/pem"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
. "github.com/ctdk/goiardi/chefcrypto"
|
||
|
. "github.com/smartystreets/goconvey/convey"
|
||
|
"io"
|
||
|
"math/big"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
testRequiredHeaders = []string{
|
||
|
"X-Ops-Timestamp",
|
||
|
"X-Ops-UserId",
|
||
|
"X-Ops-Sign",
|
||
|
"X-Ops-Content-Hash",
|
||
|
"X-Ops-Authorization-1",
|
||
|
}
|
||
|
|
||
|
mux *http.ServeMux
|
||
|
server *httptest.Server
|
||
|
client *Client
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
userid = "tester"
|
||
|
requestURL = "http://localhost:80"
|
||
|
// Generated from
|
||
|
// openssl genrsa -out privkey.pem 2048
|
||
|
// perl -pe 's/\n/\\n/g' privkey.pem
|
||
|
privateKey = `
|
||
|
-----BEGIN RSA PRIVATE KEY-----
|
||
|
MIIEpAIBAAKCAQEAx12nDxxOwSPHRSJEDz67a0folBqElzlu2oGMiUTS+dqtj3FU
|
||
|
h5lJc1MjcprRVxcDVwhsSSo9948XEkk39IdblUCLohucqNMzOnIcdZn8zblN7Cnp
|
||
|
W03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0qlDWCzC+VaxFTwOUk31MfOHJQn4y
|
||
|
fTrfuE7h3FTElLBu065SFp3dPICIEmWCl9DadnxbnZ8ASxYQ9xG7hmZduDgjNW5l
|
||
|
3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZC3ekeJ/aEeLxP/vaCSH1VYC5VsYK
|
||
|
5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZYRQIDAQABAoIBADPQol+qAsnty5er
|
||
|
PTcdHcbXLJp5feZz1dzSeL0gdxja/erfEJIhg9aGUBs0I55X69VN6h7l7K8PsHZf
|
||
|
MzzJhUL4QJJETOYP5iuVhtIF0I+DTr5Hck/5nYcEv83KAvgjbiL4ZE486IF5awnL
|
||
|
2OE9HtJ5KfhEleNcX7MWgiIHGb8G1jCqu/tH0GI8Z4cNgUrXMbczGwfbN/5Wc0zo
|
||
|
Dtpe0Tec/Fd0DLFwRiAuheakPjlVWb7AGMDX4TyzCXfMpS1ul2jk6nGFk77uQozF
|
||
|
PQUawCRp+mVS4qecgq/WqfTZZbBlW2L18/kpafvsxG8kJ7OREtrb0SloZNFHEc2Q
|
||
|
70GbgKECgYEA6c/eOrI3Uour1gKezEBFmFKFH6YS/NZNpcSG5PcoqF6AVJwXg574
|
||
|
Qy6RatC47e92be2TT1Oyplntj4vkZ3REv81yfz/tuXmtG0AylH7REbxubxAgYmUT
|
||
|
18wUAL4s3TST2AlK4R29KwBadwUAJeOLNW+Rc4xht1galsqQRb4pUzkCgYEA2kj2
|
||
|
vUhKAB7QFCPST45/5q+AATut8WeHnI+t1UaiZoK41Jre8TwlYqUgcJ16Q0H6KIbJ
|
||
|
jlEZAu0IsJxjQxkD4oJgv8n5PFXdc14HcSQ512FmgCGNwtDY/AT7SQP3kOj0Rydg
|
||
|
N02uuRb/55NJ07Bh+yTQNGA+M5SSnUyaRPIAMW0CgYBgVU7grDDzB60C/g1jZk/G
|
||
|
VKmYwposJjfTxsc1a0gLJvSE59MgXc04EOXFNr4a+oC3Bh2dn4SJ2Z9xd1fh8Bur
|
||
|
UwCLwVE3DBTwl2C/ogiN4C83/1L4d2DXlrPfInvloBYR+rIpUlFweDLNuve2pKvk
|
||
|
llU9YGeaXOiHnGoY8iKgsQKBgQDZKMOHtZYhHoZlsul0ylCGAEz5bRT0V8n7QJlw
|
||
|
12+TSjN1F4n6Npr+00Y9ov1SUh38GXQFiLq4RXZitYKu6wEJZCm6Q8YXd1jzgDUp
|
||
|
IyAEHNsrV7Y/fSSRPKd9kVvGp2r2Kr825aqQasg16zsERbKEdrBHmwPmrsVZhi7n
|
||
|
rlXw1QKBgQDBOyUJKQOgDE2u9EHybhCIbfowyIE22qn9a3WjQgfxFJ+aAL9Bg124
|
||
|
fJIEzz43fJ91fe5lTOgyMF5TtU5ClAOPGtlWnXU0e5j3L4LjbcqzEbeyxvP3sn1z
|
||
|
dYkX7NdNQ5E6tcJZuJCGq0HxIAQeKPf3x9DRKzMnLply6BEzyuAC4g==
|
||
|
-----END RSA PRIVATE KEY-----
|
||
|
`
|
||
|
// Generated from
|
||
|
// openssl rsa -in privkey.pem -pubout -out pubkey.pem
|
||
|
// perl -pe 's/\n/\\n/g' pubkey.pem
|
||
|
publicKey = `
|
||
|
-----BEGIN PUBLIC KEY-----
|
||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx12nDxxOwSPHRSJEDz67
|
||
|
a0folBqElzlu2oGMiUTS+dqtj3FUh5lJc1MjcprRVxcDVwhsSSo9948XEkk39Idb
|
||
|
lUCLohucqNMzOnIcdZn8zblN7CnpW03UwRM0iWX1HuwHnGvm6PKeqKGqplyIXYO0
|
||
|
qlDWCzC+VaxFTwOUk31MfOHJQn4yfTrfuE7h3FTElLBu065SFp3dPICIEmWCl9Da
|
||
|
dnxbnZ8ASxYQ9xG7hmZduDgjNW5l3x6/EFkpym+//D6AbWDcVJ1ovCsJL3CfH/NZ
|
||
|
C3ekeJ/aEeLxP/vaCSH1VYC5VsYK5Qg7SIa6Nth3+RZz1hYOoBJulEzwljznwoZY
|
||
|
RQIDAQAB
|
||
|
-----END PUBLIC KEY-----
|
||
|
`
|
||
|
// Generated from
|
||
|
// openssl dsaparam -out dsaparam.pem 2048
|
||
|
// openssl gendsa -out privkey.pem dsaparam.pem
|
||
|
// perl -pe 's/\n/\\n/g' privkey.pem
|
||
|
badPrivateKey = `
|
||
|
-----BEGIN DSA PRIVATE KEY-----
|
||
|
MIIDVgIBAAKCAQEApv0SsaKRWyn0IrbI6i547c/gldLQ3vB5xoSuTkVOvmD3HfuE
|
||
|
EVPKMS+XKlhgHOJy677zYNKUOIR78vfDVr1M89w19NSic81UwGGaOkrjQWOkoHaA
|
||
|
BS4046AzYKWqHWQNn9dm7WdQlbMBcBv9u+J6EqlzstPwWVaRdbAzyPtwQZRF5WfC
|
||
|
OcrQr8XpXbKsPh55FzfvFpu4KEKTY+8ynLz9uDNW2iAxj9NtRlUHQNqKQvjQsr/8
|
||
|
4pVrEBh+CnzNrmPXQIbyxV0y8WukAo3I3ZXK5nsUcJhFoVCRx4aBlp9W96mYZ7OE
|
||
|
dPCkFsoVhUNFo0jlJhMPODR1NXy77c4v1Kh6xwIhAJwFm6CQBOWJxZdGo2luqExE
|
||
|
acUG9Hkr2qd0yccgs2tFAoIBAQCQJCwASD7X9l7nZyZvJpXMe6YreGaP3VbbHCz8
|
||
|
GHs1P5exOausfJXa9gRLx2qDW0sa1ZyFUDnd2Dt810tgAhY143lufNoV3a4IRHpS
|
||
|
Fm8jjDRMyBQ/BrLBBXgpwiZ9LHBuUSeoRKY0BdyRsULmcq2OaBq9J38NUblWSe2R
|
||
|
NjQ45X6SGgUdHy3CrQtLjCA9l8+VPg3l05IBbXIhVSllP5AUmMG4T9x6M7NHEoSr
|
||
|
c7ewKSJNvc1C8+G66Kfz8xcChKcKC2z1YzvxrlcDHF+BBLw1Ppp+yMBfhQDWIZfe
|
||
|
6tpiKEEyWoyi4GkzQ+vooFIriaaL+Nnggh+iJ7BEUByHBaHnAoIBAFUxSB3bpbbp
|
||
|
Vna0HN6b+svuTCFhYi9AcmI1dcyEFKycUvZjP/X07HvX2yrL8aGxMJgF6RzPob/F
|
||
|
+SZar3u9Fd8DUYLxis6/B5d/ih7GnfPdChrDOJM1nwlferTGHXd1TBDzugpAovCe
|
||
|
JAjXiPsGmcCi9RNyoGib/FgniT7IKA7s3yJAzYSeW3wtLToSNGFJHn+TzFDBuWV4
|
||
|
KH70bpEV84JIzWo0ejKzgMBQ0Zrjcsm4lGBtzaBqGSvOrlIVFuSWFYUxrSTTxthQ
|
||
|
/JYz4ch8+HsQC/0HBuJ48yALDCVKsWq4Y21LRRJIOC25DfjwEYWWaKNGlDDsJA1m
|
||
|
Y5WF0OX+ABcCIEXhrzI1NddyFwLnfDCQ+sy6HT8/xLKXfaipd2rpn3gL
|
||
|
-----END DSA PRIVATE KEY-----
|
||
|
`
|
||
|
)
|
||
|
|
||
|
// Gave up trying to implement this myself
|
||
|
// nopCloser came from https://groups.google.com/d/msg/golang-nuts/J-Y4LtdGNSw/wDSYbHWIKj0J
|
||
|
// yay for sharing
|
||
|
// nopCloser creates a io.ReadCloser to satisfy the request.Body input
|
||
|
type nopCloser struct {
|
||
|
io.Reader
|
||
|
}
|
||
|
|
||
|
func (nopCloser) Close() error { return nil }
|
||
|
|
||
|
func setup() {
|
||
|
mux = http.NewServeMux()
|
||
|
server = httptest.NewServer(mux)
|
||
|
client, _ = NewClient(&Config{
|
||
|
Name: userid,
|
||
|
Key: privateKey,
|
||
|
BaseURL: server.URL,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func teardown() {
|
||
|
server.Close()
|
||
|
}
|
||
|
|
||
|
func createServer() *httptest.Server {
|
||
|
return httptest.NewServer(http.HandlerFunc(checkHeader))
|
||
|
}
|
||
|
|
||
|
// publicKeyFromString parses an RSA public key from a string
|
||
|
func publicKeyFromString(key []byte) (*rsa.PublicKey, error) {
|
||
|
block, _ := pem.Decode(key)
|
||
|
if block == nil {
|
||
|
return nil, fmt.Errorf("block size invalid for '%s'", string(key))
|
||
|
}
|
||
|
rsaKey, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return rsaKey.(*rsa.PublicKey), nil
|
||
|
}
|
||
|
|
||
|
func makeAuthConfig() (*AuthConfig, error) {
|
||
|
pk, err := PrivateKeyFromString([]byte(privateKey))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
ac := &AuthConfig{
|
||
|
PrivateKey: pk,
|
||
|
ClientName: userid,
|
||
|
}
|
||
|
return ac, nil
|
||
|
}
|
||
|
|
||
|
func TestAuthConfig(t *testing.T) {
|
||
|
_, err := makeAuthConfig()
|
||
|
if err != nil {
|
||
|
t.Error("Failed to create AuthConfig struct from privatekeys and stuff", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestBase64BlockEncodeNoLimit(t *testing.T) {
|
||
|
ac, _ := makeAuthConfig()
|
||
|
var content string
|
||
|
for _, key := range []string{"header1", "header2", "header3"} {
|
||
|
content += fmt.Sprintf("%s:blahblahblah\n", key)
|
||
|
}
|
||
|
content = strings.TrimSuffix(content, "\n")
|
||
|
|
||
|
signature, _ := GenerateSignature(ac.PrivateKey, content)
|
||
|
Base64BlockEncode(signature, 0)
|
||
|
}
|
||
|
|
||
|
func TestSignRequestBadSignature(t *testing.T) {
|
||
|
ac, err := makeAuthConfig()
|
||
|
request, err := http.NewRequest("GET", requestURL, nil)
|
||
|
ac.PrivateKey.PublicKey.N = big.NewInt(23234728432324)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
if err == nil {
|
||
|
t.Fatal("failed to generate failed signature")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSignRequestNoBody(t *testing.T) {
|
||
|
setup()
|
||
|
defer teardown()
|
||
|
ac, err := makeAuthConfig()
|
||
|
request, err := client.NewRequest("GET", requestURL, nil)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
if err != nil {
|
||
|
t.Fatal("failed to generate RequestHeaders")
|
||
|
}
|
||
|
count := 0
|
||
|
for _, requiredHeader := range testRequiredHeaders {
|
||
|
for header := range request.Header {
|
||
|
if strings.ToLower(requiredHeader) == strings.ToLower(header) {
|
||
|
count++
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if count != len(testRequiredHeaders) {
|
||
|
t.Error("apiRequestHeaders didn't return all of testRequiredHeaders")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSignRequestBody(t *testing.T) {
|
||
|
ac, err := makeAuthConfig()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
setup()
|
||
|
defer teardown()
|
||
|
|
||
|
// Gave up trying to implement this myself
|
||
|
// nopCloser came from https://groups.google.com/d/msg/golang-nuts/J-Y4LtdGNSw/wDSYbHWIKj0J
|
||
|
// yay for sharing
|
||
|
requestBody := strings.NewReader("somecoolbodytext")
|
||
|
request, err := client.NewRequest("GET", requestURL, requestBody)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
if err != nil {
|
||
|
t.Fatal("failed to generate RequestHeaders")
|
||
|
}
|
||
|
count := 0
|
||
|
for _, requiredHeader := range testRequiredHeaders {
|
||
|
for header := range request.Header {
|
||
|
if strings.ToLower(requiredHeader) == strings.ToLower(header) {
|
||
|
count++
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if count != len(testRequiredHeaders) {
|
||
|
t.Error("apiRequestHeaders didn't return all of testRequiredHeaders")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// <3 goiardi
|
||
|
// Test our headers as goiardi would
|
||
|
// https://github.com/ctdk/goiardi/blob/master/authentication/authentication.go
|
||
|
// func checkHeader(user_id string, r *http.Request) string {
|
||
|
func checkHeader(rw http.ResponseWriter, req *http.Request) {
|
||
|
user_id := req.Header.Get("X-OPS-USERID")
|
||
|
// Since we don't have a real client or user to check against,
|
||
|
// we'll just verify that input user = output user
|
||
|
// user, err := actor.GetReqUser(user_id)
|
||
|
// if err != nil {
|
||
|
if user_id != userid {
|
||
|
fmt.Fprintf(rw, "Failed to authenticate as %s", user_id)
|
||
|
}
|
||
|
|
||
|
contentHash := req.Header.Get("X-OPS-CONTENT-HASH")
|
||
|
if contentHash == "" {
|
||
|
fmt.Fprintf(rw, "no content hash provided")
|
||
|
}
|
||
|
|
||
|
authTimestamp := req.Header.Get("x-ops-timestamp")
|
||
|
if authTimestamp == "" {
|
||
|
fmt.Fprintf(rw, "no timestamp header provided")
|
||
|
}
|
||
|
// TODO: Will want to implement this later
|
||
|
// else {
|
||
|
// // check the time stamp w/ allowed slew
|
||
|
// tok, terr := checkTimeStamp(authTimestamp, config.Config.TimeSlewDur)
|
||
|
// if !tok {
|
||
|
// return terr
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// Eventually this may be put to some sort of use, but for now just
|
||
|
// make sure that it's there. Presumably eventually it would be used to
|
||
|
// use algorithms other than sha1 for hashing the body, or using a
|
||
|
// different version of the header signing algorithm.
|
||
|
xopssign := req.Header.Get("x-ops-sign")
|
||
|
var apiVer string
|
||
|
var hashChk []string
|
||
|
if xopssign == "" {
|
||
|
fmt.Fprintf(rw, "missing X-Ops-Sign header")
|
||
|
} else {
|
||
|
re := regexp.MustCompile(`version=(\d+\.\d+)`)
|
||
|
shaRe := regexp.MustCompile(`algorithm=(\w+)`)
|
||
|
if verChk := re.FindStringSubmatch(xopssign); verChk != nil {
|
||
|
apiVer = verChk[1]
|
||
|
if apiVer != "1.0" && apiVer != "1.1" {
|
||
|
fmt.Fprintf(rw, "Bad version number '%s' in X-Ops-Header", apiVer)
|
||
|
|
||
|
}
|
||
|
} else {
|
||
|
fmt.Fprintf(rw, "malformed version in X-Ops-Header")
|
||
|
}
|
||
|
|
||
|
// if algorithm is missing, it uses sha1. Of course, no other
|
||
|
// hashing algorithm is supported yet...
|
||
|
if hashChk = shaRe.FindStringSubmatch(xopssign); hashChk != nil {
|
||
|
if hashChk[1] != "sha1" {
|
||
|
fmt.Fprintf(rw, "Unsupported hashing algorithm '%s' specified in X-Ops-Header", hashChk[1])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
signedHeaders, sherr := assembleSignedHeader(req)
|
||
|
if sherr != nil {
|
||
|
fmt.Fprintf(rw, sherr.Error())
|
||
|
}
|
||
|
|
||
|
_, err := HeaderDecrypt(publicKey, signedHeaders)
|
||
|
if err != nil {
|
||
|
fmt.Fprintf(rw, "unexpected header decryption error '%s'", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRequest(t *testing.T) {
|
||
|
ac, err := makeAuthConfig()
|
||
|
server := createServer()
|
||
|
defer server.Close()
|
||
|
setup()
|
||
|
defer teardown()
|
||
|
|
||
|
request, err := client.NewRequest("GET", server.URL, nil)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
if err != nil {
|
||
|
t.Fatal("failed to generate RequestHeaders")
|
||
|
}
|
||
|
|
||
|
client := &http.Client{}
|
||
|
response, err := client.Do(request)
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
|
||
|
if response.StatusCode != 200 {
|
||
|
t.Error("Non 200 return code: " + response.Status)
|
||
|
}
|
||
|
|
||
|
buf := new(bytes.Buffer)
|
||
|
buf.ReadFrom(response.Body)
|
||
|
bodyStr := buf.String()
|
||
|
|
||
|
if bodyStr != "" {
|
||
|
t.Error(bodyStr)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestRequestToEndpoint(t *testing.T) {
|
||
|
ac, err := makeAuthConfig()
|
||
|
server := createServer()
|
||
|
defer server.Close()
|
||
|
|
||
|
requestBody := strings.NewReader("somecoolbodytext")
|
||
|
request, err := client.NewRequest("GET", server.URL+"/clients", requestBody)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
if err != nil {
|
||
|
t.Fatal("failed to generate RequestHeaders")
|
||
|
}
|
||
|
|
||
|
client := &http.Client{}
|
||
|
response, err := client.Do(request)
|
||
|
if err != nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
|
||
|
if response.StatusCode != 200 {
|
||
|
t.Error("Non 200 return code: " + response.Status)
|
||
|
}
|
||
|
|
||
|
buf := new(bytes.Buffer)
|
||
|
buf.ReadFrom(response.Body)
|
||
|
bodyStr := buf.String()
|
||
|
|
||
|
if bodyStr != "" {
|
||
|
t.Error(bodyStr)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// More Goiardi <3
|
||
|
func assembleSignedHeader(r *http.Request) (string, error) {
|
||
|
sHeadStore := make(map[int]string)
|
||
|
authHeader := regexp.MustCompile(`(?i)^X-Ops-Authorization-(\d+)`)
|
||
|
for k := range r.Header {
|
||
|
if c := authHeader.FindStringSubmatch(k); c != nil {
|
||
|
/* Have to put it into a map first, then sort, in case
|
||
|
* the headers don't come out in the right order */
|
||
|
// skipping this error because we shouldn't even be
|
||
|
// able to get here with something that won't be an
|
||
|
// integer. Famous last words, I'm sure.
|
||
|
i, _ := strconv.Atoi(c[1])
|
||
|
sHeadStore[i] = r.Header.Get(k)
|
||
|
}
|
||
|
}
|
||
|
if len(sHeadStore) == 0 {
|
||
|
return "", errors.New("no authentication headers found")
|
||
|
}
|
||
|
|
||
|
sH := make([]string, len(sHeadStore))
|
||
|
sHlimit := len(sH)
|
||
|
for k, v := range sHeadStore {
|
||
|
if k > sHlimit {
|
||
|
return "", errors.New("malformed authentication headers")
|
||
|
}
|
||
|
sH[k-1] = v
|
||
|
}
|
||
|
signedHeaders := strings.Join(sH, "")
|
||
|
|
||
|
return signedHeaders, nil
|
||
|
}
|
||
|
|
||
|
func assembleHeaderToCheck(r *http.Request) string {
|
||
|
|
||
|
// To validate the signature it seems to be very particular
|
||
|
// Would like to use this loop to generate the content
|
||
|
// But it causes validation to fail.. so we do it explicitly
|
||
|
|
||
|
// authHeader := regexp.MustCompile(`(?i)^X-Ops-Authorization-(\d+)`)
|
||
|
// acceptEncoding := regexp.MustCompile(`(?i)^Accept-Encoding`)
|
||
|
// userAgent := regexp.MustCompile(`(?i)^User-Agent`)
|
||
|
//
|
||
|
// var content string
|
||
|
// for key, value := range r.Header {
|
||
|
// if !authHeader.MatchString(key) && !acceptEncoding.MatchString(key) && !userAgent.MatchString(key) {
|
||
|
// content += fmt.Sprintf("%s:%s\n", key, value)
|
||
|
// }
|
||
|
// }
|
||
|
// return content
|
||
|
var content string
|
||
|
content += fmt.Sprintf("%s:%s\n", "Method", r.Header.Get("Method"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "Hashed Path", r.Header.Get("Hashed Path"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "Accept", r.Header.Get("Accept"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "X-Chef-Version", r.Header.Get("X-Chef-Version"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "X-Ops-Timestamp", r.Header.Get("X-Ops-Timestamp"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "X-Ops-Userid", r.Header.Get("X-Ops-Userid"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "X-Ops-Sign", r.Header.Get("X-Ops-Sign"))
|
||
|
content += fmt.Sprintf("%s:%s\n", "X-Ops-Content-Hash", r.Header.Get("X-Ops-Content-Hash"))
|
||
|
return content
|
||
|
}
|
||
|
|
||
|
func TestGenerateHash(t *testing.T) {
|
||
|
input, output := HashStr("hi"), "witfkXg0JglCjW9RssWvTAveakI="
|
||
|
|
||
|
Convey("correctly hashes a given input string", t, func() {
|
||
|
So(input, ShouldEqual, output)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// BUG(fujin): @bradbeam: this doesn't make sense to me.
|
||
|
func TestGenerateSignatureError(t *testing.T) {
|
||
|
ac, _ := makeAuthConfig()
|
||
|
|
||
|
// BUG(fujin): what about the 'hi' string is not meant to be signable?
|
||
|
sig, err := GenerateSignature(ac.PrivateKey, "hi")
|
||
|
|
||
|
Convey("sig should be empty?", t, func() {
|
||
|
So(sig, ShouldNotBeEmpty)
|
||
|
})
|
||
|
|
||
|
Convey("errors for an unknown reason to fujin", t, func() {
|
||
|
So(err, ShouldBeNil)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestRequestError(t *testing.T) {
|
||
|
ac, err := makeAuthConfig()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
// Gave up trying to implement this myself
|
||
|
// nopCloser came from https://groups.google.com/d/msg/golang-nuts/J-Y4LtdGNSw/wDSYbHWIKj0J
|
||
|
// yay for sharing
|
||
|
requestBody := nopCloser{bytes.NewBufferString("somecoolbodytext")}
|
||
|
request, err := http.NewRequest("GET", requestURL, requestBody)
|
||
|
|
||
|
err = ac.SignRequest(request)
|
||
|
|
||
|
// BUG(fujin): This should actually error not bubble nil?
|
||
|
Convey("should not sign a request with missing required information", t, func() {
|
||
|
So(err, ShouldBeNil)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestNewClient(t *testing.T) {
|
||
|
cfg := &Config{Name: "testclient", Key: privateKey, SkipSSL: false}
|
||
|
c, err := NewClient(cfg)
|
||
|
if err != nil {
|
||
|
t.Error("Couldn't make a valid client...\n", err)
|
||
|
}
|
||
|
// simple validation on the created client
|
||
|
if c.Auth.ClientName != "testclient" {
|
||
|
t.Error("unexpected client name: ", c.Auth.ClientName)
|
||
|
}
|
||
|
|
||
|
// Bad PEM should be an error
|
||
|
cfg = &Config{Name: "blah", Key: "not a key", SkipSSL: false}
|
||
|
c, err = NewClient(cfg)
|
||
|
if err == nil {
|
||
|
t.Error("Built a client from a bad key string")
|
||
|
}
|
||
|
|
||
|
// Not a proper key should be an error
|
||
|
cfg = &Config{Name: "blah", Key: badPrivateKey, SkipSSL: false}
|
||
|
c, err = NewClient(cfg)
|
||
|
if err == nil {
|
||
|
t.Error("Built a client from a bad key string")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewRequest(t *testing.T) {
|
||
|
var err error
|
||
|
server := createServer()
|
||
|
cfg := &Config{Name: "testclient", Key: privateKey, SkipSSL: false}
|
||
|
c, _ := NewClient(cfg)
|
||
|
defer server.Close()
|
||
|
|
||
|
request, err := c.NewRequest("GET", server.URL, nil)
|
||
|
if err != nil {
|
||
|
t.Error("HRRRM! we tried to make a request but it failed :`( ", err)
|
||
|
}
|
||
|
|
||
|
resp, err := c.Do(request, nil)
|
||
|
if resp.StatusCode != 200 {
|
||
|
t.Error("Non 200 return code: ", resp.Status)
|
||
|
}
|
||
|
|
||
|
// This should fail because we've got an invalid URI
|
||
|
_, err = c.NewRequest("GET", "%gh&%ij", nil)
|
||
|
if err == nil {
|
||
|
t.Error("This terrible request thing should fail and it didn't")
|
||
|
}
|
||
|
|
||
|
// This should fail because there is no TOODLES! method :D
|
||
|
request, err = c.NewRequest("TOODLES!", "", nil)
|
||
|
_, err = c.Do(request, nil)
|
||
|
if err == nil {
|
||
|
t.Error("This terrible request thing should fail and it didn't")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDo_badjson(t *testing.T) {
|
||
|
setup()
|
||
|
defer teardown()
|
||
|
|
||
|
mux.HandleFunc("/hashrocket", func(w http.ResponseWriter, r *http.Request) {
|
||
|
fmt.Fprintf(w, " pigthrusters => 100% ")
|
||
|
})
|
||
|
|
||
|
stupidData := struct{}{}
|
||
|
request, err := client.NewRequest("GET", "hashrocket", nil)
|
||
|
_, err = client.Do(request, &stupidData)
|
||
|
if err == nil {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
|
||
|
}
|