Files
tsrun/internal/buildblob/buildblob.go
T
dmgnr e588b9a527
Deploy tsrun / deploy (push) Failing after 15s
feat: add tsrun bootstrap service and deploy
2026-07-22 06:23:21 +00:00

133 lines
3.1 KiB
Go

package buildblob
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"os"
"golang.org/x/crypto/argon2"
)
const (
keyLen = 32
defaultTime = 3
defaultMem = 64 * 1024
defaultCPU = 4
)
type Manifest struct {
OAuthClientID string `json:"oauthClientID"`
OAuthClientSecret string `json:"oauthClientSecret"`
Tags []string `json:"tags"`
Users []ManifestUser `json:"users"`
}
type ManifestUser struct {
Username string `json:"username"`
Password string `json:"password"`
}
type Blob struct {
Version int `json:"version"`
Users []BlobUser `json:"users"`
KDF KDFParams `json:"kdf"`
}
type BlobUser struct {
Username string `json:"username"`
Salt []byte `json:"salt"`
Nonce []byte `json:"nonce"`
Ciphertext []byte `json:"ciphertext"`
}
type KDFParams struct {
Time uint32 `json:"time"`
Memory uint32 `json:"memory"`
Threads uint8 `json:"threads"`
KeyLen uint32 `json:"keyLen"`
}
type payload struct {
OAuthClientID string `json:"oauthClientID"`
OAuthClientSecret string `json:"oauthClientSecret"`
Tags []string `json:"tags"`
}
func BuildFromFile(path string) ([]byte, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var manifest Manifest
if err := json.Unmarshal(raw, &manifest); err != nil {
return nil, err
}
return Build(manifest)
}
func Build(manifest Manifest) ([]byte, error) {
if manifest.OAuthClientID == "" || manifest.OAuthClientSecret == "" {
return nil, errors.New("manifest must include oauthClientID and oauthClientSecret")
}
if len(manifest.Users) == 0 {
return nil, errors.New("manifest must include at least one user")
}
pt, err := json.Marshal(payload{
OAuthClientID: manifest.OAuthClientID,
OAuthClientSecret: manifest.OAuthClientSecret,
Tags: manifest.Tags,
})
if err != nil {
return nil, err
}
params := KDFParams{Time: defaultTime, Memory: defaultMem, Threads: defaultCPU, KeyLen: keyLen}
blob := Blob{Version: 1, KDF: params}
seen := map[string]struct{}{}
for _, user := range manifest.Users {
if user.Username == "" || user.Password == "" {
return nil, errors.New("each user must include username and password")
}
if _, ok := seen[user.Username]; ok {
return nil, fmt.Errorf("duplicate username %q", user.Username)
}
seen[user.Username] = struct{}{}
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
key := argon2.IDKey([]byte(user.Password), salt, params.Time, params.Memory, params.Threads, params.KeyLen)
block, err := aes.NewCipher(key)
zero(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
blob.Users = append(blob.Users, BlobUser{
Username: user.Username,
Salt: salt,
Nonce: nonce,
Ciphertext: aead.Seal(nil, nonce, pt, nil),
})
}
return json.Marshal(blob)
}
func zero(buf []byte) {
for i := range buf {
buf[i] = 0
}
}