feat(access-token): add ephemeral access-token resource (#1068)
* feat(access-token): add ephemeral access-token resource Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
This commit is contained in:
parent
368b8d55be
commit
0e9b97a513
12 changed files with 733 additions and 5 deletions
|
|
@ -0,0 +1,253 @@
|
|||
package access_token
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/clients"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
)
|
||||
|
||||
//go:embed testdata/service_account.json
|
||||
var testServiceAccountKey string
|
||||
|
||||
func startMockTokenServer() *httptest.Server {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
resp := clients.TokenResponseBody{
|
||||
AccessToken: "mock_access_token",
|
||||
RefreshToken: "mock_refresh_token",
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
|
||||
Scope: "mock_scope",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(resp)
|
||||
})
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func generatePrivateKey() (string, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
privateKeyPEM := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
}
|
||||
return string(pem.EncodeToMemory(privateKeyPEM)), nil
|
||||
}
|
||||
|
||||
func writeTempPEMFile(t *testing.T, pemContent string) string {
|
||||
t.Helper()
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "stackit_test_private_key_*.pem")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.WriteString(pemContent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
})
|
||||
|
||||
return tmpFile.Name()
|
||||
}
|
||||
|
||||
func TestGetAccessToken(t *testing.T) {
|
||||
mockServer := startMockTokenServer()
|
||||
t.Cleanup(mockServer.Close)
|
||||
|
||||
privateKey, err := generatePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
setupEnv func()
|
||||
cleanupEnv func()
|
||||
cfgFactory func() *config.Configuration
|
||||
expectError bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
description: "should return token when service account key passed by value",
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
ServiceAccountKey: testServiceAccountKey,
|
||||
PrivateKey: privateKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should return token when service account key is loaded from file path",
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
ServiceAccountKeyPath: "testdata/service_account.json",
|
||||
PrivateKey: privateKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should fail when private key is invalid",
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
ServiceAccountKey: "invalid-json",
|
||||
PrivateKey: "invalid-PEM",
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: true,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
description: "should return token when service account key is set via env",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", testServiceAccountKey)
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY")
|
||||
},
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
PrivateKey: privateKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should return token when service account key path is set via env",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH", "testdata/service_account.json")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH")
|
||||
},
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
PrivateKey: privateKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should return token when private key is set via env",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv("STACKIT_PRIVATE_KEY", privateKey)
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv("STACKIT_PRIVATE_KEY")
|
||||
},
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
ServiceAccountKey: testServiceAccountKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should return token when private key path is set via env",
|
||||
setupEnv: func() {
|
||||
// Write temp file and set env
|
||||
tmpFile := writeTempPEMFile(t, privateKey)
|
||||
_ = os.Setenv("STACKIT_PRIVATE_KEY_PATH", tmpFile)
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv("STACKIT_PRIVATE_KEY_PATH")
|
||||
},
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
ServiceAccountKey: testServiceAccountKey,
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: false,
|
||||
expected: "mock_access_token",
|
||||
},
|
||||
{
|
||||
description: "should fail when no service account key or private key is set",
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: true,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
description: "should fail when no service account key or private key is set via env",
|
||||
setupEnv: func() {
|
||||
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY")
|
||||
_ = os.Unsetenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH")
|
||||
_ = os.Unsetenv("STACKIT_PRIVATE_KEY")
|
||||
_ = os.Unsetenv("STACKIT_PRIVATE_KEY_PATH")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
// Restore original environment variables
|
||||
},
|
||||
cfgFactory: func() *config.Configuration {
|
||||
return &config.Configuration{
|
||||
TokenCustomUrl: mockServer.URL,
|
||||
}
|
||||
},
|
||||
expectError: true,
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
if tt.setupEnv != nil {
|
||||
tt.setupEnv()
|
||||
}
|
||||
if tt.cleanupEnv != nil {
|
||||
defer tt.cleanupEnv()
|
||||
}
|
||||
|
||||
cfg := tt.cfgFactory()
|
||||
|
||||
token, err := getAccessToken(cfg)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none for test case '%s'", tt.description)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("did not expect error but got: %v for test case '%s'", err, tt.description)
|
||||
}
|
||||
if token != tt.expected {
|
||||
t.Errorf("expected token '%s', got '%s' for test case '%s'", tt.expected, token, tt.description)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue