-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Continuous integration for Gorjun Add travis file for CI and libgorjun for tests.
- Loading branch information
emli
committed
Feb 19, 2018
1 parent
f47e27b
commit f11073b
Showing
13 changed files
with
568 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,20 @@ | ||
language: perl | ||
perl: | ||
- "5.22" | ||
before_install: | ||
- git clone git://github.com/travis-perl/helpers ~/travis-perl-helpers | ||
- source ~/travis-perl-helpers/init --auto | ||
language: go | ||
|
||
sudo: enabled | ||
go: | ||
- 1.10 | ||
script: | ||
- cd gorjun-perl-cli; prove -v t/test_cover.t | ||
- go get | ||
- make | ||
- sudo apt-get install systemd | ||
- sudo cp -f /home/travis/gopath/src/github.com/subutai-io/gorjun/libgorjun/gorjun.service /etc/systemd/system/gorjun.service | ||
- sudo systemctl daemon-reload | ||
- sudo systemctl start gorjun.service | ||
- sudo systemctl status gorjun.service | ||
- sudo apt install -y rng-tools | ||
- sudo rngd -r /dev/urandom | ||
- gpg --gen-key --batch /home/travis/gopath/src/github.com/subutai-io/gorjun/libgorjun/gpg.txt | ||
- sudo chmod +x /home/travis/gopath/src/github.com/subutai-io/gorjun/libgorjun/register.sh | ||
- cd /home/travis/gopath/src/github.com/emli/subutai-io/libgorjun/; ./register.sh | ||
- cd /home/travis/gopath/src/github.com/emli/subutai-io/libgorjun; go get github.com/stretchr/testify/assert; | ||
- go test -v |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package gorjun | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
|
||
"golang.org/x/crypto/openpgp" | ||
"golang.org/x/crypto/openpgp/armor" | ||
"golang.org/x/crypto/openpgp/packet" | ||
"mime/multipart" | ||
) | ||
|
||
func (g *GorjunServer) RegisterUser(username string, publicKey string) (string, error) { | ||
var b bytes.Buffer | ||
w := multipart.NewWriter(&b) | ||
|
||
fw, err := w.CreateFormField("name") | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to create name form: %v", err) | ||
} | ||
if _, err = fw.Write([]byte(username)); err != nil { | ||
return "", fmt.Errorf("Failed to write token: %v", err) | ||
} | ||
if fw, err = w.CreateFormField("key"); err != nil { | ||
return "", fmt.Errorf("Failed to create key form field: %v", err) | ||
} | ||
if _, err = fw.Write([]byte(publicKey)); err != nil { | ||
return "", fmt.Errorf("Failed to write token: %v", err) | ||
} | ||
|
||
w.Close() | ||
|
||
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/kurjun/rest/auth/register", g.Hostname), &b) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to create HTTP request: %v", err) | ||
} | ||
req.Header.Set("Content-Type", w.FormDataContentType()) | ||
client := &http.Client{} | ||
res, err := client.Do(req) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to execute HTTP request: %v", err) | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
return "", fmt.Errorf("Registration failed. Server returned %s error", res.Status) | ||
} | ||
response, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to read response body: %v", err) | ||
} | ||
return string(response), nil | ||
} | ||
|
||
// AuthenticateUser will try to authenticate user by downloading his token code, signing it with GPG | ||
// and sending it back to server to get user token | ||
// If passphrase is not empty, PGP will try to decrypt the private key before signing the code | ||
// if gpgdir is empty, the default ($HOME/.gnupg) will be used | ||
func (g *GorjunServer) AuthenticateUser() error { | ||
err := g.GetAuthTokenCode() | ||
if err != nil { | ||
return err | ||
} | ||
sign, err := g.SignToken(g.TokenCode) | ||
if err != nil { | ||
return err | ||
} | ||
err = g.GetActiveToken(sign) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// GetAuthTokenCode is a first step of authentication - it requests a special code from the server. | ||
// This code needs to be PGP-signed later | ||
func (g *GorjunServer) GetAuthTokenCode() error { | ||
resp, err := http.Get(fmt.Sprintf("http://%s/kurjun/rest/auth/token?user=%s", g.Hostname, g.Username)) | ||
if err != nil { | ||
return fmt.Errorf("Failed to retrieve unsigned token: %v", err) | ||
} | ||
data, err := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
if err != nil { | ||
return fmt.Errorf("Failed to read body from %s: %v", g.Hostname, err) | ||
} | ||
g.TokenCode = string(data) | ||
return nil | ||
} | ||
|
||
// GetActiveToken will send signed message to server and return active token | ||
// that will be used for authneticated requests | ||
func (g *GorjunServer) GetActiveToken(signed string) error { | ||
signed = "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n" + g.TokenCode + "\n" + signed + "\n" | ||
form := url.Values{ | ||
"message": {signed}, | ||
"user": {g.Username}, | ||
} | ||
body := bytes.NewBufferString(form.Encode()) | ||
resp, err := http.Post(fmt.Sprintf("http://%s/kurjun/rest/auth/token", g.Hostname), "application/x-www-form-urlencoded", body) | ||
if err != nil { | ||
return fmt.Errorf("Failed to retrieve active token: %v", err) | ||
} | ||
data, err := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
if err != nil { | ||
return fmt.Errorf("Failed to read body from %s: %v", g.Hostname, err) | ||
} | ||
g.Token = string(data) | ||
if len(g.Token) != 64 { | ||
reason := g.Token | ||
g.Token = "" | ||
return fmt.Errorf("Failed to retrieve active token: %s", reason) | ||
} | ||
return nil | ||
} | ||
|
||
func (g *GorjunServer) GetKeyByEmail(keyring openpgp.EntityList, email string) *openpgp.Entity { | ||
for _, entity := range keyring { | ||
for _, ident := range entity.Identities { | ||
if ident.UserId.Email == email { | ||
return entity | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// SignToken will sign with GnuPG provided token and return signed version | ||
func (g *GorjunServer) SignToken(token string) (string, error) { | ||
if g.GPGDirectory == "" { | ||
return "", fmt.Errorf("GPG Directory was not specified") | ||
} | ||
// GPG may have two variants of key storage - in secring.gpg/pubring.gpg for older versions | ||
// and for pubring.kbx and separate directory for private key in version of GnuPG 2.1+ | ||
pubringPath := g.GPGDirectory + "/pubring.gpg" | ||
if _, err := os.Stat(pubringPath); os.IsNotExist(err) { | ||
pubringPath = g.GPGDirectory + "/pubring.kbx" | ||
} | ||
if _, err := os.Stat(pubringPath); os.IsNotExist(err) { | ||
return "", fmt.Errorf("Can't find pubring.gpg nor pubring.kbx") | ||
} | ||
pukFile, err := os.Open(g.GPGDirectory + "/pubring.gpg") | ||
defer pukFile.Close() | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to open public keyring file: %v", err) | ||
} | ||
pubring, err := openpgp.ReadKeyRing(pukFile) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to read public keyring: %v", err) | ||
} | ||
publicKey := g.GetKeyByEmail(pubring, g.Email) | ||
if publicKey == nil { | ||
return "", fmt.Errorf("Public key for %s was not found", g.Email) | ||
} | ||
|
||
priFile, err := os.Open(g.GPGDirectory + "/secring.gpg") | ||
defer priFile.Close() | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to open private keyring file: %v", err) | ||
} | ||
secring, err := openpgp.ReadKeyRing(priFile) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to read private keyring: %v", err) | ||
} | ||
privateKey := g.GetKeyByEmail(secring, g.Email) | ||
if privateKey == nil { | ||
return "", fmt.Errorf("Private key for %s was not found", g.Email) | ||
} | ||
if g.Passphrase != "" { | ||
privateKey.PrivateKey.Decrypt([]byte(g.Passphrase)) | ||
} | ||
outBuf := new(bytes.Buffer) | ||
err = openpgp.ArmoredDetachSign(outBuf, privateKey, strings.NewReader(token), nil) | ||
if err != nil { | ||
return "", fmt.Errorf("Failed to sign token: %s", err) | ||
} | ||
return outBuf.String(), nil | ||
} | ||
|
||
func (g *GorjunServer) decodePrivateKey() (*packet.PrivateKey, error) { | ||
in, err := os.Open(g.GPGDirectory + "/secring.gpg") | ||
if err != nil { | ||
in.Close() | ||
return nil, err | ||
} | ||
|
||
block, err := armor.Decode(in) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to decode GPG Armor: %s", err) | ||
} | ||
|
||
if block.Type != openpgp.PrivateKeyType { | ||
return nil, fmt.Errorf("Invalid private key file") | ||
} | ||
|
||
reader := packet.NewReader(block.Body) | ||
pkt, err := reader.Next() | ||
if err != nil { | ||
return nil, fmt.Errorf("Error reading private key") | ||
} | ||
|
||
key, success := pkt.(*packet.PrivateKey) | ||
if !success { | ||
return nil, fmt.Errorf("Error parsing private key") | ||
} | ||
return key, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package gorjun | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
"math/rand" | ||
"net/http" | ||
"io/ioutil" | ||
"encoding/json" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var r *rand.Rand // Rand for this package. | ||
|
||
func init() { | ||
r = rand.New(rand.NewSource(time.Now().UnixNano())) | ||
} | ||
|
||
func RandomString(strlen int) string { | ||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789" | ||
result := make([]byte, strlen) | ||
for i := range result { | ||
result[i] = chars[r.Intn(len(chars))] | ||
} | ||
return string(result) | ||
} | ||
|
||
func TestGorjunServer_AuthenticateUser(t *testing.T) { | ||
g := NewGorjunServer(); | ||
_, err := g.RegisterUser("tester", "publickey") | ||
if err != nil { | ||
t.Errorf("Failed to register user: %v", err) | ||
} | ||
} | ||
|
||
func TestGorjunServer_RegisterUserWithMultipleKeys(t *testing.T) { | ||
g := NewGorjunServer(); | ||
randomUserName := RandomString(10) | ||
for i:= 1; i <= 100; i++ { | ||
randomKey := RandomString(100) | ||
_, err := g.RegisterUser(randomUserName, randomKey) | ||
if err != nil { | ||
t.Errorf("Failed to register user: %v", err) | ||
} | ||
resp, err := http.Get(fmt.Sprintf("http://%s/kurjun/rest/auth/keys?user=%s", g.Hostname, randomUserName)) | ||
if err != nil { | ||
fmt.Errorf("Failed to retrieve user keys: %v", err) | ||
} | ||
data, err := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
if err != nil { | ||
fmt.Errorf("Failed to read body from %s: %v", g.Hostname, err) | ||
} | ||
var f []GorjunFile | ||
err = json.Unmarshal(data, &f) | ||
if err != nil { | ||
fmt.Errorf("Failed to unmarshal contents from %s: %v", g.Hostname, err) | ||
} | ||
assert.Equal(t, i, len(f), "Numbers of key should be equal") | ||
} | ||
} | ||
|
||
func TestGorjunServer_GetKeysByOwner(t *testing.T) { | ||
g := NewGorjunServer(); | ||
artifactTypes := [3]string{"template", "raw", "apt"} | ||
for i:= 0; i < len(artifactTypes); i++ { | ||
resp, err := http.Get(fmt.Sprintf("http://%s/kurjun/rest/" + artifactTypes[i] + "/list", g.Hostname)) | ||
if err != nil { | ||
fmt.Errorf("Failed to retrieve list of %s s: %v", artifactTypes[i], err) | ||
} | ||
data, err := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
var artifacts []GorjunFile | ||
err = json.Unmarshal(data, &artifacts) | ||
for j:= 0; j < len(artifacts); j++ { | ||
if len(artifacts[j].Owner) > 0 { | ||
resp, _ := http.Get(fmt.Sprintf("http://%s/kurjun/rest/auth/keys?user=%s", g.Hostname, artifacts[j].Owner[0])) | ||
data, _ := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
var keys []Keys | ||
err = json.Unmarshal(data, &keys) | ||
assert.NotEqual(t, len(keys), 0, "Keys of existing user should be greater than zero") | ||
} | ||
} | ||
} | ||
|
||
} | ||
func TestGetAuthTokenCode(t *testing.T) { | ||
g := NewGorjunServer(); | ||
err := g.GetAuthTokenCode() | ||
if err != nil { | ||
t.Errorf("Failed to retrieve token: %v", err) | ||
} | ||
if len(g.TokenCode) != 32 { | ||
t.Errorf("Token length doesn't equals 32 symbols: %d", len(g.TokenCode)) | ||
} | ||
} | ||
|
||
func TestGetActiveToken(t *testing.T) { | ||
g := NewGorjunServer() | ||
err := g.GetAuthTokenCode() | ||
if err != nil { | ||
t.Errorf("Failed to retrieve token: %v", err) | ||
} | ||
fmt.Printf("Token code: %s\n", g.TokenCode) | ||
sign, err := g.SignToken(g.TokenCode) | ||
if err != nil { | ||
t.Errorf("Failed to sign token code: %v", err) | ||
} | ||
fmt.Printf("Signed token code: %s\n", sign) | ||
err = g.GetActiveToken(sign) | ||
if err != nil { | ||
t.Errorf("Failed to get active token: %v", err) | ||
} | ||
fmt.Printf("Active token: %s, len: %d\n", g.Token, len(g.Token)) | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Oops, something went wrong.