Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ my.instance.dir.org/
├── internal_auth_password
├── postgres_password
├── superadmin
├── vote_key
├── cert_crt # (if HTTPS enabled)
└── cert_key
```
Expand Down
6 changes: 6 additions & 0 deletions internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
// PgPasswordFile contains the PostgreSQL database password
PgPasswordFile string = "postgres_password"

// VoteKeyFile contains the vote key secret
VoteKeyFile string = "vote_key"

// AuthTokenKey contains the authentication token secret
AuthTokenKey string = "auth_token_key"

Expand Down Expand Up @@ -82,6 +85,9 @@ const (
// DefaultPostgresPasswordLength is the default length for database passwords
DefaultPostgresPasswordLength int = 40

// DefaultVoteKeyLength is the default length for vote key secret
DefaultVoteKeyLength int = 40

// DefaultSecretBytesLength is the number of random bytes used for base64-encoded secrets.
// These 32 bytes produce a 44-character base64 string used for:
// - auth_token_key
Expand Down
2 changes: 1 addition & 1 deletion internal/grpc/server/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func (s *OsmanageServiceServer) CreateInstance(ctx context.Context, req *pb.CreateInstanceRequest) (*pb.CreateInstanceResponse, error) {
if err := create.CreateInstance(req.InstanceDir, req.DbPassword, req.SuperadminPassword); err != nil {
if err := create.CreateInstance(req.InstanceDir, req.DbPassword, req.SuperadminPassword, req.VoteKey); err != nil {
return &pb.CreateInstanceResponse{Success: false, Error: err.Error()}, nil
}
return &pb.CreateInstanceResponse{Success: true}, nil
Expand Down
20 changes: 16 additions & 4 deletions internal/instance/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ This command:
2. Sets all secret files to 600 permissions
3. Writes the database password to postgres_password
4. Writes the superadmin password to superadmin
5. Writes the vote key to vote_key

The secrets directory must already exist (created by 'setup' command).

Examples:
osmanage create ./my.instance.dir.org --db-password "mydbpass" --superadmin-password "myadminpass"
osmanage create ./my.instance.dir.org --db-password "$(cat db.txt)" --superadmin-password "$(cat admin.txt)"`
osmanage create ./my.instance.dir.org --db-password "mydbpass" --superadmin-password "myadminpass" --vote-key "myvotekey"
osmanage create ./my.instance.dir.org --db-password "$(cat db.txt)" --superadmin-password "$(cat admin.txt)" --vote-key "$(cat vote_key.txt)"`
)

func Cmd() *cobra.Command {
Expand All @@ -39,16 +40,18 @@ func Cmd() *cobra.Command {

dbPassword := cmd.Flags().String("db-password", "", "PostgreSQL database password (required)")
superadminPassword := cmd.Flags().String("superadmin-password", "", "Superadmin password (required)")
voteKey := cmd.Flags().String("vote-key", "", "Vote Key (required)")

_ = cmd.MarkFlagRequired("db-password")
_ = cmd.MarkFlagRequired("superadmin-password")
_ = cmd.MarkFlagRequired("vote-key")

cmd.RunE = func(cmd *cobra.Command, args []string) error {
logger.Info("=== K8S CREATE INSTANCE ===")
instanceDir := args[0]
logger.Debug("Instance directory: %s", instanceDir)

if err := CreateInstance(instanceDir, *dbPassword, *superadminPassword); err != nil {
if err := CreateInstance(instanceDir, *dbPassword, *superadminPassword, *voteKey); err != nil {
return fmt.Errorf("creating instance: %w", err)
}

Expand All @@ -60,13 +63,16 @@ func Cmd() *cobra.Command {
}

// CreateInstance sets up the secrets directory with the provided passwords
func CreateInstance(instanceDir, dbPassword, superadminPassword string) error {
func CreateInstance(instanceDir, dbPassword, superadminPassword, voteKey string) error {
if strings.TrimSpace(dbPassword) == "" {
return fmt.Errorf("db_password cannot be empty")
}
if strings.TrimSpace(superadminPassword) == "" {
return fmt.Errorf("superadmin_password cannot be empty")
}
if strings.TrimSpace(voteKey) == "" {
return fmt.Errorf("vote_key cannot be empty")
}

secretsDir := filepath.Join(instanceDir, constants.SecretsDirName)

Expand All @@ -93,6 +99,12 @@ func CreateInstance(instanceDir, dbPassword, superadminPassword string) error {
return fmt.Errorf("writing superadmin password: %w", err)
}

voteKeyPath := filepath.Join(secretsDir, constants.VoteKeyFile)
logger.Debug("Writing vote key to: %s", voteKeyPath)
if err := os.WriteFile(voteKeyPath, []byte(voteKey), constants.SecretFilePerm); err != nil {
return fmt.Errorf("writing vote key: %w", err)
}

logger.Info("Passwords configured successfully")
return nil
}
Expand Down
15 changes: 13 additions & 2 deletions internal/instance/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func TestCreateInstance(t *testing.T) {
existingSecrets := map[string]string{
constants.PgPasswordFile: "old-db-password",
constants.AdminSecretsFile: "old-admin-password",
constants.VoteKeyFile: "old-vote-key",
constants.InternalAuthPassword: "some-auth-key",
}

Expand All @@ -156,8 +157,9 @@ func TestCreateInstance(t *testing.T) {
// Run createInstance
dbPassword := "new-database-password"
superadminPassword := "new-superadmin-password"
voteKey := "new-vote-key"

err = CreateInstance(tmpDir, dbPassword, superadminPassword)
err = CreateInstance(tmpDir, dbPassword, superadminPassword, voteKey)
if err != nil {
t.Fatalf("createInstance failed: %v", err)
}
Expand All @@ -180,6 +182,15 @@ func TestCreateInstance(t *testing.T) {
t.Errorf("superadmin = %q, want %q", string(adminContent), superadminPassword)
}

// Verify vote_key was overwritten
voteKeyContent, err := os.ReadFile(filepath.Join(secretsDir, constants.VoteKeyFile))
if err != nil {
t.Fatalf("Failed to read vote_key: %v", err)
}
if string(voteKeyContent) != voteKey {
t.Errorf("vote_key = %q, want %q", string(voteKeyContent), voteKey)
}

// Verify other secrets were not touched
authContent, err := os.ReadFile(filepath.Join(secretsDir, constants.InternalAuthPassword))
if err != nil {
Expand Down Expand Up @@ -234,7 +245,7 @@ func TestCreateInstance_SecretsDirectoryNotExist(t *testing.T) {
})

// Don't create secrets directory - should fail
err = CreateInstance(tmpDir, "password", "admin")
err = CreateInstance(tmpDir, "password", "admin", "key")
if err == nil {
t.Error("Expected error when secrets directory doesn't exist, got nil")
}
Expand Down
1 change: 1 addition & 0 deletions internal/instance/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var defaultSecrets = []SecretSpec{
{constants.AuthCookieKey, randomSecret},
{constants.InternalAuthPassword, randomSecret},
{constants.PgPasswordFile, func() ([]byte, error) { return randomString(constants.DefaultPostgresPasswordLength) }},
{constants.VoteKeyFile, func() ([]byte, error) { return randomString(constants.DefaultVoteKeyLength) }},
{constants.AdminSecretsFile, func() ([]byte, error) { return randomString(constants.DefaultSuperadminPasswordLength) }},
}

Expand Down
21 changes: 21 additions & 0 deletions internal/instance/setup/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ func TestDefaultSecrets(t *testing.T) {
constants.AuthCookieKey,
constants.InternalAuthPassword,
constants.PgPasswordFile,
constants.VoteKeyFile,
constants.AdminSecretsFile,
}

Expand Down Expand Up @@ -285,6 +286,19 @@ func TestDefaultSecrets(t *testing.T) {
}
}

// Test vote_key generates proper string
for _, spec := range defaultSecrets {
if spec.Name == constants.VoteKeyFile {
pwd, err := spec.Generator()
if err != nil {
t.Errorf("vote_key generator error = %v", err)
}
if len(pwd) != constants.DefaultVoteKeyLength {
t.Errorf("Expected length %d, got %d", constants.DefaultVoteKeyLength, len(pwd))
}
}
}

// Test that base64-encoded secrets still work as expected
for _, spec := range defaultSecrets {
if spec.Name == constants.AuthTokenKey || spec.Name == constants.AuthCookieKey || spec.Name == constants.InternalAuthPassword {
Expand Down Expand Up @@ -384,6 +398,13 @@ tag: {{ .defaults.tag }}
if len(postgresPwd) != constants.DefaultPostgresPasswordLength {
t.Errorf("Expected postgres password length %d, got %d", constants.DefaultPostgresPasswordLength, len(postgresPwd))
}

// 6. Check vote_key has correct length
voteKeyPath := filepath.Join(secretsDir, constants.VoteKeyFile)
voteKey, _ := os.ReadFile(voteKeyPath)
if len(voteKey) != constants.DefaultVoteKeyLength {
t.Errorf("Expected vote_key length %d, got %d", constants.DefaultVoteKeyLength, len(voteKey))
}
})

t.Run("template processing with camelCase", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions proto/osmanage.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ message CreateInstanceRequest {
string instance_dir = 1;
string db_password = 2;
string superadmin_password = 3;
string vote_key = 4;
}

message CreateInstanceResponse {
Expand Down
15 changes: 12 additions & 3 deletions proto/osmanage/osmanage.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion proto/osmanage/osmanage_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading