Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions internal/common/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package common

const DefaultServerSecret = "changeme"
const DefaultLoginServerEndpoint = "https://login.thand.io"
13 changes: 5 additions & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ import (
"github.com/thand-io/agent/internal/sessions"
)

const DefaultServerSecret = "changeme"
const DefaultLoginServerEndpoint = "https://login.thand.io"

var ErrNoActiveLoginSession = fmt.Errorf(
"you must login first. No valid session found to sync with login server")

Expand Down Expand Up @@ -525,12 +522,12 @@ func setDefaults(v *viper.Viper) {
v.SetDefault("environment.ephemeral", environment.IsEphemeralEnvironment())

// Environment config defaults
v.SetDefault("environment.config.timeout", "5s") // Timeout for any config fetch operations
v.SetDefault("environment.config.key", DefaultServerSecret) // Default encryption key name
v.SetDefault("environment.config.salt", DefaultServerSecret) // Default encryption salt
v.SetDefault("environment.config.timeout", "5s") // Timeout for any config fetch operations
v.SetDefault("environment.config.key", common.DefaultServerSecret) // Default encryption key name
v.SetDefault("environment.config.salt", common.DefaultServerSecret) // Default encryption salt

// Login server defaults
v.SetDefault("login.endpoint", DefaultLoginServerEndpoint)
v.SetDefault("login.endpoint", common.DefaultLoginServerEndpoint)
v.SetDefault("login.base", "/")

// Server defaults
Expand Down Expand Up @@ -570,7 +567,7 @@ func setDefaults(v *viper.Viper) {
v.SetDefault("oidc.scopes", []string{"openid", "profile", "email"})

// Session defaults
v.SetDefault("secret", DefaultServerSecret)
v.SetDefault("secret", common.DefaultServerSecret)

// Logging defaults
v.SetDefault("logging.level", "info")
Expand Down
6 changes: 5 additions & 1 deletion internal/config/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ func (c *Config) GetProviders() ProviderConfig {
func (c *Config) GetServices() models.ServicesClientImpl {

c.initializeServiceClientOnce.Do(func() {
newClient := services.NewServicesClient(&c.Environment, &c.Services)
newClient := services.NewServicesClient(
&c.Environment,
&c.Services,
&c.Secret,
)
err := newClient.Initialize()
if err != nil {
logrus.WithError(err).Fatalf("Failed to initialize services client: %v", err)
Expand Down
11 changes: 11 additions & 0 deletions internal/config/services/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"sync"

"github.com/sirupsen/logrus"
"github.com/thand-io/agent/internal/common"
"github.com/thand-io/agent/internal/config/services/temporal"
"github.com/thand-io/agent/internal/models"
)

type localClient struct {
environment *models.EnvironmentConfig
config *models.ServicesConfig
secret *string

encrypt models.EncryptionImpl
vault models.VaultImpl
Expand All @@ -22,10 +24,12 @@ type localClient struct {
func NewServicesClient(
environment *models.EnvironmentConfig,
config *models.ServicesConfig,
secret *string,
) *localClient {
return &localClient{
environment: environment,
config: config,
secret: secret,
}
}

Expand All @@ -37,6 +41,13 @@ func (e *localClient) GetEnvironmentConfig() *models.EnvironmentConfig {
return e.environment
}

func (e *localClient) GetSecret() string {
if e.secret == nil {
return common.DefaultServerSecret
}
return *e.secret
}

func (e *localClient) Initialize() error {

logrus.Infof("Creating services client")
Expand Down
11 changes: 9 additions & 2 deletions internal/config/services/encrypt/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"encoding/json"
"fmt"
"io"
"strings"

"github.com/sirupsen/logrus"
"github.com/thand-io/agent/internal/common"
"github.com/thand-io/agent/internal/models"
"golang.org/x/crypto/pbkdf2"
)
Expand Down Expand Up @@ -40,8 +42,13 @@ func deriveKey(password string, salt string) []byte {

func (l *localVault) Initialize() error {

masterPassword := l.config.GetStringWithDefault("password", "changeme")
salt := l.config.GetStringWithDefault("salt", "changeme")
masterPassword := l.config.GetStringWithDefault("password", common.DefaultServerSecret)
salt := l.config.GetStringWithDefault("salt", common.DefaultLoginServerEndpoint)

if strings.EqualFold(masterPassword, common.DefaultServerSecret) ||
strings.EqualFold(salt, common.DefaultServerSecret) {
Comment thread
hughneale marked this conversation as resolved.
logrus.Warningln("local encryption service configured with default secrets. See https://docs.thand.io/configuration/file.html#encryption-service")
Comment thread
hughneale marked this conversation as resolved.
}

l.key = deriveKey(masterPassword, salt)

Expand Down
12 changes: 12 additions & 0 deletions internal/config/services/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ func (e *localClient) configureEncryption() models.EncryptionImpl {
case string(models.Local):
fallthrough
default:

// Do we have our password and salt? If not try and provide a
// better alternative than the default

if !configValues.HasString("salt") {
configValues.SetKeyWithValue("salt", e.GetEnvironmentConfig().Hostname)
}

if !configValues.HasString("password") && len(e.GetSecret()) > 0 {
configValues.SetKeyWithValue("password", e.GetSecret())
Comment thread
hughneale marked this conversation as resolved.
}

localEncrypt := encrypt.NewLocalEncryptionFromConfig(configValues)
return localEncrypt
}
Expand Down
6 changes: 3 additions & 3 deletions internal/daemon/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/thand-io/agent/internal/config"
"github.com/thand-io/agent/internal/common"
"github.com/thand-io/agent/internal/models"
sessionManager "github.com/thand-io/agent/internal/sessions"
)
Expand All @@ -34,8 +34,8 @@ func (s *Server) SetupMiddleware() gin.HandlerFunc {
// Ok so we're running in server mode, check if the hostname
// has been configured

notDefaultLoginEndpoint := s.Config.GetLoginServerUrl() != config.DefaultLoginServerEndpoint
notDefaultSecret := s.Config.Secret != config.DefaultServerSecret
notDefaultLoginEndpoint := s.Config.GetLoginServerUrl() != common.DefaultLoginServerEndpoint
notDefaultSecret := s.Config.Secret != common.DefaultServerSecret
hasEncryptionService := s.Config.GetServices().HasEncryption()

// If any configuration is missing, show setup page
Expand Down
11 changes: 8 additions & 3 deletions internal/daemon/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
"github.com/thand-io/agent/internal/config"
"github.com/thand-io/agent/internal/models"
"github.com/thand-io/agent/internal/workflows/manager"
"go.temporal.io/sdk/client"
"go.temporal.io/api/workflowservice/v1"
)

//go:embed static/*
Expand Down Expand Up @@ -448,8 +448,13 @@ func (s *Server) healthHandler(c *gin.Context) {
services := s.Config.GetServices()

if services.HasTemporal() {
_, err := services.GetTemporal().GetClient().CheckHealth(
c.Request.Context(), &client.CheckHealthRequest{})

// Use count rather than health check as temporal cloud
// does not support external health checks
_, err := services.GetTemporal().GetClient().CountWorkflow(
Comment thread
hughneale marked this conversation as resolved.
c.Request.Context(),
&workflowservice.CountWorkflowExecutionsRequest{},
)
if err != nil {

logrus.WithError(err).Error("Temporal service health check failed")
Expand Down
5 changes: 3 additions & 2 deletions internal/daemon/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/thand-io/agent/internal/common"
"github.com/thand-io/agent/internal/config"
)

Expand Down Expand Up @@ -31,8 +32,8 @@ func (s *Server) setupPage(c *gin.Context) {
}

// Check if the login server URL is still the default
defaultLoginEndpoint := s.Config.GetLoginServerUrl() == config.DefaultLoginServerEndpoint
defaultSecret := s.Config.Secret == config.DefaultServerSecret
defaultLoginEndpoint := s.Config.GetLoginServerUrl() == common.DefaultLoginServerEndpoint
defaultSecret := s.Config.Secret == common.DefaultServerSecret

defaultServicesTemporalHost := false
defaultServicestemporalPort := false
Expand Down
57 changes: 43 additions & 14 deletions internal/daemon/static/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{template "header" .}}
<main>
<div class="container" style="width: 100%;">
<div class="hero">
<div class="hero" x-data="healthCheck()">
<h1>{{.ServiceName}}</h1>
<p>A minimal, secure role-based access control and workflow management system for modern infrastructure.</p>

Expand All @@ -11,8 +11,8 @@ <h1>{{.ServiceName}}</h1>

<div class="status-card">
<div class="status-indicator">
<div class="status-dot" id="status-dot"></div>
<span>Service Status: <span id="status">{{.Status}}</span></span>
<div class="status-dot" :class="statusClass"></div>
<span>Service Status: <span x-text="statusText">{{.Status}}</span></span>
</div>
<div style="margin-top: 0.5rem; font-size: 0.875rem; color: hsl(var(--muted-foreground));">
Version: {{.Version}} | Host: {{.Config.Server.Host}}:{{.Config.Server.Port}}
Expand All @@ -23,16 +23,45 @@ <h1>{{.ServiceName}}</h1>
</main>

<script>
// Check service health
fetch('{{.Config.Server.Health.Path}}')
.then(response => response.json())
.then(data => {
document.getElementById('status').textContent = 'Online';
document.getElementById('status-dot').classList.remove('offline');
})
.catch(error => {
document.getElementById('status').textContent = 'Offline';
document.getElementById('status-dot').classList.add('offline');
});
function healthCheck() {
return {
status: null,
statusText: '{{.Status}}',
statusClass: '',

init() {
this.checkHealth();
},

async checkHealth() {
try {
const response = await fetch('{{.Config.Server.Health.Path}}');
const data = await response.json();
this.status = data.status;

switch (data.status) {
case 'healthy':
this.statusText = 'Healthy';
this.statusClass = 'healthy';
break;
case 'degraded':
this.statusText = 'Degraded';
this.statusClass = 'degraded';
break;
case 'unhealthy':
this.statusText = 'Unhealthy';
this.statusClass = 'unhealthy';
break;
default:
this.statusText = 'Unknown';
this.statusClass = 'offline';
}
} catch (error) {
this.statusText = 'Offline';
this.statusClass = 'offline';
}
}
}
}
</script>
{{template "footer" .}}
12 changes: 12 additions & 0 deletions internal/daemon/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ main {
background-color: #ef4444;
}

.status-dot.healthy {
background-color: #22c55e;
}

.status-dot.degraded {
background-color: #eab308;
}

.status-dot.unhealthy {
background-color: #ef4444;
}

@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
Expand Down
10 changes: 10 additions & 0 deletions internal/models/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ func (pc *BasicConfig) GetString(key string) (string, bool) {
return "", false
}

func (pc *BasicConfig) HasString(key string) bool {
if pc == nil {
return false
}
if _, ok := (*pc)[key]; ok {
return ok
}
return false
Comment thread
hughneale marked this conversation as resolved.
Outdated
}

func (pc *BasicConfig) GetInt(key string) (int, bool) {
if pc == nil {
return 0, false
Expand Down