init commit.

parents
*
!*/
!*.*
!*.go
!*.yaml
!id_*
!Makefile
!LICENSE
!Dockerfile
!Jenkinsfile
postgres/*
mariadb/*
*.sha256sum
*.exe
.vscode/*
build/*
/.project
checktest.out
coverage.out
cover.out
config.local.yaml
manifests
\ No newline at end of file
APPNAME := aim-client
APPSRC := ./cmd/$(APPNAME)
GITCOMMITHASH := $(shell git log --max-count=1 --pretty="format:%h" HEAD)
GITCOMMIT := -X main.gitcommit=$(GITCOMMITHASH)
VERSIONTAG := $(shell git describe --tags --abbrev=0)
VERSION := -X main.appversion=$(VERSIONTAG)
BUILDTIMEVALUE := $(shell date +%Y-%m-%dT%H:%M:%S%z)
BUILDTIME := -X main.buildtime=$(BUILDTIMEVALUE)
LDFLAGS := '-extldflags "-static" -d -s -w $(GITCOMMIT) $(VERSION) $(BUILDTIME)'
LDFLAGS_WINDOWS := '-extldflags "-static" -s -w $(GITCOMMIT) $(VERSION) $(BUILDTIME)'
all:info clean build
clean:
rm -rf build
info:
@echo - appname: $(APPNAME)
@echo - version: $(VERSIONTAG)
@echo - commit: $(GITCOMMITHASH)
@echo - buildtime: $(BUILDTIMEVALUE)
dep:
@go get -v -d ./...
build-linux: info dep
@echo Building for linux
@mkdir -p build/linux
@CGO_ENABLED=0 \
GOOS=linux \
go build -o build/linux/$(APPNAME)-$(VERSIONTAG)-$(GITCOMMITHASH) -a -ldflags $(LDFLAGS) $(APPSRC)
@cp build/linux/$(APPNAME)-$(VERSIONTAG)-$(GITCOMMITHASH) build/linux/$(APPNAME)
image:
@echo Creating docker image
@docker build -t $(APPNAME):$(VERSIONTAG)-$(GITCOMMITHASH) .
package client
import (
gcrypto "crypto"
"crypto/sha256"
"encoding/base64"
"net/http"
"gitlab.com/denic-id/aim-client/config"
"gitlab.com/denic-id/aim-client/crypto"
"gitlab.com/denic-id/aim-client/models"
"gitlab.com/denic-id/aim-client/utils"
jose "gopkg.in/square/go-jose.v2"
)
type Client struct {
httpClient *http.Client
key *jose.JSONWebKey
config *config.AppConfig
id string
initialized bool
}
func Init(appconfig *config.AppConfig) (*Client, error) {
c := new(Client)
key, err := crypto.CreateJwk(crypto.TYPE_ECDSA)
if err != nil {
return nil, err
}
if keyerr := crypto.WriteKeyToFile(key, appconfig.Client.KeyFile); keyerr != nil {
return nil, keyerr
}
c.config = appconfig
c.key = key
c.initialized = true
c.httpClient = new(http.Client)
exists, _, err := c.existsAtAuthority()
if err != nil {
return nil, err
}
if !exists {
err := c.CreateAtAuthority()
if err != nil {
return nil, err
}
}
return c, nil
}
func NewFromConfig(appconfig *config.AppConfig) (*Client, error) {
c := new(Client)
key, err := crypto.ReadKeyFromFile(appconfig.Client.KeyFile)
if err != nil {
return nil, err
}
c.key = key
c.config = appconfig
c.initialized = true
c.httpClient = new(http.Client)
return c, nil
}
func (c *Client) CreateAtAuthority() error {
if !c.initialized {
return NotInitializedError
}
exists, _, err := c.existsAtAuthority()
if err != nil {
return err
}
if exists {
return nil
}
agent, err := c.postNewAgent()
if err != nil {
return err
}
c.updateClientKeyAfterRegistration(agent.ID)
return nil
}
func (c *Client) updateClientKeyAfterRegistration(kid string) error {
c.key.KeyID = kid
return crypto.WriteKeyToFile(c.key, c.config.Client.KeyFile)
}
func (c *Client) CreateAndSaveIdentityAuthz(id, locale string) (*models.IdentityAuthz, error) {
authz, err := c.postNewIdentityAuthz(id, locale)
if err != nil {
return nil, err
}
err = utils.ToYamlFile(authz, c.config.AuthzDir+"/"+id+".authz.yaml")
if err != nil {
return nil, err
}
return authz, nil
}
func (c *Client) CreateAcmeChallenge(token string) (string, error) {
thumbprint, err := c.key.Thumbprint(gcrypto.SHA256)
if err != nil {
return "", err
}
thumbprintStr := base64.RawURLEncoding.EncodeToString(thumbprint)
hasher := sha256.New()
hasher.Write([]byte(token + "." + thumbprintStr))
hash := hasher.Sum(nil)
hashStr := base64.RawURLEncoding.EncodeToString(hash)
return hashStr, nil
}
func (c *Client) GetIdentityAuthz(id string) (*models.IdentityAuthz, error) {
authzAtAuthority, err := c.getIdentityAuthz(id)
if err != nil {
return nil, err
}
err = utils.ToYamlFile(authzAtAuthority, c.config.AuthzDir+"/"+id+".authz.yaml")
if err != nil {
return nil, err
}
return authzAtAuthority, nil
}
func (c *Client) PostAcmeSetupDone(id string) (*models.IdentityAuthz, error) {
authzAtAuthority, err := c.postAcmeSetUp(id)
if err != nil {
return nil, err
}
return authzAtAuthority, nil
}
func (c *Client) GetIdentity(id string) (*models.Identity, error) {
identity, err := c.getIdentity(id)
if err != nil {
return nil, err
}
err = utils.ToYamlFile(identity, c.config.AuthzDir+"/"+id+".yaml")
if err != nil {
return nil, err
}
return identity, nil
}
func (c *Client) ResetIdentityCredentials(id string) (*models.IdentitiesCredentialsResetResponse, error) {
return c.resetIdentityCredentials(id)
}
package client
import (
"fmt"
"testing"
"gitlab.com/denic-id/aim-client/utils"
"gitlab.com/denic-id/aim-client/config"
"github.com/dchest/uniuri"
"github.com/stretchr/testify/assert"
)
var appconfig = &config.AppConfig{
Client: &config.ClientConfig{
KeyFile: "testkey.json",
Name: fmt.Sprintf("denic-test-%s", uniuri.NewLen(8)),
FullName: "client-full-name",
Email: "mail@client",
},
AimURL: "https://id.staging.denic.de/aim/v1",
}
func TestNewClient(t *testing.T) {
t.Run("init a new client", func(t *testing.T) {
c, err := Init(appconfig)
if !assert.NoError(t, err, "new client must be initialized.") {
return
}
if !assert.Equal(t, true, c.key.Valid(), "client must have a valid key.") {
return
}
})
t.Run("create a new client", func(t *testing.T) {
_, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
})
t.Run("exists at authority", func(t *testing.T) {
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
exists, _, err := c.existsAtAuthority()
if !assert.NoError(t, err, "check client for existance at authority must not fail.") {
return
}
if !assert.Equal(t, true, exists, "client must exist at authority") {
return
}
})
t.Run("create at authority", func(t *testing.T) {
appconfig.Client.Name = fmt.Sprintf("denic-test-%s", uniuri.NewLen(8))
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
err = c.CreateAtAuthority()
if !assert.NoError(t, err, "Client must be created at the authority.") {
return
}
exists, _, err := c.existsAtAuthority()
if !assert.NoError(t, err, "check client for existance at authority must not fail.") {
return
}
if !assert.Equal(t, true, exists, "client must exist at authority") {
return
}
})
}
func TestClientExists(t *testing.T) {
t.Run("existing at authority", func(t *testing.T) {
//appconfig.Client.Name = fmt.Sprintf("denic-test-%s", uniuri.NewLen(8))
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
exists, _, err := c.existsAtAuthority()
if !assert.NoError(t, err, "check client for existance at authority must not fail.") {
return
}
if !assert.Equal(t, true, exists, "client must exist at authority") {
return
}
})
}
func TestClientPostNewIdAuthz(t *testing.T) {
t.Run("create new authz", func(t *testing.T) {
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
id := fmt.Sprintf("%s.foobar.bla", uniuri.NewLen(8))
authz, err := c.postNewIdentityAuthz(id, "de")
if !assert.NoError(t, err, "post new id must not fail.") {
return
}
challenge, err := c.CreateAcmeChallenge(authz.Challenge.Token)
fmt.Println(challenge)
})
id := fmt.Sprintf("%s.foobar.bla", uniuri.NewLen(8))
t.Run("create new authz and write yaml", func(t *testing.T) {
appconfig.AuthzDir = "."
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
authz, err := c.postNewIdentityAuthz(id, "de")
if !assert.NoError(t, err, "post new id must not fail.") {
return
}
err = utils.AuthzToYamlFile(authz, ".")
if !assert.NoError(t, err, "Auth file must be created.") {
return
}
challenge, err := c.CreateAcmeChallenge(authz.Challenge.Token)
fmt.Println(challenge)
})
t.Run("create and save new authz", func(t *testing.T) {
appconfig.AuthzDir = "."
id := fmt.Sprintf("%s.foobar.bla", uniuri.NewLen(8))
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
authz, err := c.CreateAndSaveIdentityAuthz(id, "de")
if !assert.NoError(t, err, "post new id must not fail.") {
return
}
challenge, err := c.CreateAcmeChallenge(authz.Challenge.Token)
fmt.Println(challenge)
})
t.Run("load authz from file and get authz", func(t *testing.T) {
appconfig.AuthzDir = "."
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
authz, err := utils.AuthzFromYamlFile(".", id)
if !assert.NoError(t, err, "authz must be loaded from file") {
return
}
authzAtAuthority, err := c.getIdentityAuthz(authz.Identifier)
if !assert.NoError(t, err, "must get authz from authority") {
return
}
if !assert.Equal(t, authz.Challenge.Token, authzAtAuthority.Challenge.Token, "Authz must be equal") {
return
}
})
}
func TestClientPostIdAuthzReady(t *testing.T) {
appconfig.AuthzDir = "."
id := fmt.Sprintf("%s.foobar.bla", uniuri.NewLen(8))
c, err := NewFromConfig(appconfig)
if !assert.NoError(t, err, "New client must be created") {
return
}
_, err = c.CreateAndSaveIdentityAuthz(id, "de")
if !assert.NoError(t, err, "post new id must not fail.") {
return
}
t.Run("post id ready", func(t *testing.T) {
authz, err := c.postAcmeSetUp(id)
if !assert.NoError(t, err, "must get authz from authority") {
return
}
if assert.Equal(t, nil, authz.Challenge.Validated, "Fucked up.") {
return
}
})
}
package client
import "github.com/sbreitf1/errors"
var (
GenericClientError = errors.New("GenericClientError").Msg("Error: %s")
NewAgentError = errors.New("NewClientError").Msg("failed to create new client: %s")
NotInitializedError = errors.New("NotInitializedError").Msg("client is not initialized.").Make()
FailedCreateAgentError = errors.New("FailedCreateClientError").Msg("failed to create agent at authority: %s")
FailedCreateIdAuthzError = errors.New("FailedCreateIdAuthz").Msg("failed to create authz for identity: %s")
HttpClientError = errors.New("HttpClientError").Msg("http request failed: %s")
FailedIdAuthzSetUpError = errors.New("FailedIdAuthzSetUpError").Msg("failed to post id set up: %s")
GetIdentityError = errors.New("GetIdentityError").Msg("failed to get identity: %s")
)
package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"gitlab.com/denic-id/aim-client/models"
"gitlab.com/denic-id/aim-client/utils"
"gitlab.com/denic-id/aim-client/crypto"
)
func getBodyString(resp *http.Response) string {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err.Error()
}
return string(body)
}
func getBodyBytes(resp *http.Response) []byte {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil
}
return body
}
func (c *Client) newRequest(method, url, payload string, withKeyID, embedKey bool) (*http.Request, error) {
req, err := http.NewRequest(method, url, strings.NewReader(payload))
if err != nil {
return nil, err
}
s, err := crypto.NewSigner(c.key)
if err != nil {
return nil, err
}
s.SetOption("b64", false)
s.SetOption("url", url)
s.SetOption("method", method)
if withKeyID {
s.SetOption("kid", c.key.KeyID)
}
s.EmbedKey(true)
jws, err := s.SignPayload(payload)
if err != nil {
return nil, err
}
header, _, err := s.DetachPayload(jws)
req.Header.Add("jws-detached-signature", header)
req.Header.Add("Content-Type", "application/json")
return req, nil
}
func (c *Client) existsAtAuthority() (bool, *http.Response, error) {
url := fmt.Sprintf("%s/%s/%s", c.config.AimURL, "agents", c.key.KeyID)
r, err := c.newRequest(http.MethodGet, url, "", true, false)
if err != nil {
return false, nil, nil
}
resp, err := c.httpClient.Do(r)
if err != nil {
return false, nil, err
}
if resp.StatusCode != 200 {
return false, resp, nil
}
return true, resp, nil
}
func (c *Client) postNewAgent() (*models.Agent, error) {
url := fmt.Sprintf("%s/%s", c.config.AimURL, "agents")
var agent models.Agent
agent.Name = c.config.Client.Name
agent.FullName = c.config.Client.FullName
agent.Email = c.config.Client.Email
payloadbytes, err := json.Marshal(agent)
if err != nil {
return nil, err
}
r, err := c.newRequest(http.MethodPost, url, string(payloadbytes), false, true)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
body := getBodyBytes(resp)
if resp.StatusCode != 201 {
return nil, FailedCreateAgentError.Args(string(body)).Make()
}
json.Unmarshal(body, &agent)
return &agent, nil
}
func (c *Client) postNewIdentityAuthz(identifier, locale string) (*models.IdentityAuthz, error) {
var idauthz models.IdentityAuthzRequest
idauthz.Identifier = identifier
idauthz.Locale = "de"
url := fmt.Sprintf("%s/%s", c.config.AimURL, "authz")
payloadbytes, err := json.Marshal(idauthz)
if err != nil {
return nil, GenericClientError.Args(err.Error()).Make()
}
r, err := c.newRequest(http.MethodPost, url, string(payloadbytes), true, true)
if err != nil {
return nil, HttpClientError.Args(err.Error()).Make()
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, HttpClientError.Args(err.Error()).Make()
}
body := getBodyBytes(resp)
if resp.StatusCode != 201 {
return nil, FailedCreateIdAuthzError.Args(string(body)).Make()
}
var idauthzresponse models.IdentityAuthz
json.Unmarshal(body, &idauthzresponse)
return &idauthzresponse, nil
}
func (c *Client) getIdentityAuthz(id string) (*models.IdentityAuthz, error) {
authz, err := utils.AuthzFromYamlFile(c.config.AuthzDir, id)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/%s/%d", c.config.AimURL, "authz", authz.ID)
r, err := c.newRequest(http.MethodGet, url, "", true, false)
if err != nil {
return nil, HttpClientError.Args(err.Error()).Make()
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, HttpClientError.Args(err.Error()).Make()
}
body := getBodyBytes(resp)
if resp.StatusCode != 200 {
return nil, FailedCreateIdAuthzError.Args(string(body)).Make()
}
var idauthzresponse models.IdentityAuthz
json.Unmarshal(body, &idauthzresponse)
return &idauthzresponse, nil
}
func (c *Client) postAcmeSetUp(id string) (*models.IdentityAuthz, error) {
authz, err := utils.AuthzFromYamlFile(c.config.AuthzDir, id)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/%s/%d", c.config.AimURL, "authz", authz.ID)
r, err := c.newRequest(http.MethodPost, url, "", true, false)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
body := getBodyBytes(resp)
if resp.StatusCode != 200 {
return nil, FailedIdAuthzSetUpError.Args(string(body)).Make()
}
var idauthzresponse models.IdentityAuthz
json.Unmarshal(body, &idauthzresponse)
return &idauthzresponse, nil
}
func (c *Client) getIdentity(id string) (*models.Identity, error) {
url := fmt.Sprintf("%s/%s/%s", c.config.AimURL, "identities", id)
r, err := c.newRequest(http.MethodGet, url, "", true, false)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
body := getBodyBytes(resp)
if resp.StatusCode != 200 {
return nil, GetIdentityError.Args(string(body)).Make()
}
var identity models.Identity
json.Unmarshal(body, &identity)
return &identity, nil
}
func (c *Client) resetIdentityCredentials(id string) (*models.IdentitiesCredentialsResetResponse, error) {
identity := new(models.Identity)
err := utils.FromYamlFile(identity, c.config.AuthzDir+"/"+id+".yaml")
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/%s/%s", c.config.AimURL, "identities", "credentialsReset")
payload := fmt.Sprintf("{\"sub\":\"%s\"}", identity.Subject)
r, err := c.newRequest(http.MethodPost, url, payload, true, false)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
body := getBodyBytes(resp)
if resp.StatusCode != 200 {
return nil, GetIdentityError.Args(string(body)).Make()
}
var respObj models.IdentitiesCredentialsResetResponse
json.Unmarshal(body, &respObj)
return &respObj, nil
}
package main
import (
"bufio"
"fmt"
"os"
"strings"
yaml "gopkg.in/yaml.v2"
"gitlab.com/denic-id/aim-client/utils"
"gitlab.com/denic-id/aim-client/client"
"gitlab.com/denic-id/aim-client/config"
"github.com/alecthomas/kingpin"
"github.com/sebidude/configparser"
)
var (
gitcommit string
appversion string
buildtime string