Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0d49fd8
Added logging library to copybara sync (#3692)
dylanratcliffe Jan 27, 2026
60aedff
fix, changetimeline entries have status sensitive names (#3688)
tphoney Jan 28, 2026
dd6d9e2
Capture the true ctx deadline in HandleQuery (#3714)
DavidS-ovm Jan 29, 2026
e540792
Refactor AccountConfig: Unroll to columns and remove custom blast rad…
DavidS-ovm Jan 29, 2026
6001590
Update otel handling for SDP (#3717)
DavidS-ovm Jan 29, 2026
210870f
fix(deps): update google.golang.org/genproto digest to 8636f87 (#3729)
renovate[bot] Jan 30, 2026
a5dfe57
fix(deps): update github.com/serpapi/serpapi-golang digest to 0e41c79…
renovate[bot] Jan 30, 2026
e14f5cd
chore(deps): update alpine docker tag to v3.23.3 (#3731)
renovate[bot] Jan 30, 2026
894b44b
fix(deps): update google.golang.org/genproto/googleapis/rpc digest to…
renovate[bot] Jan 30, 2026
1b7d71c
chore(deps): update terraform (#3735)
renovate[bot] Jan 30, 2026
8338f17
fix(deps): update go (#3736)
renovate[bot] Jan 30, 2026
7d11f23
Eng 2340 support cloud asset inventory in gcp migrate kms (#3722)
DavidS-ovm Jan 30, 2026
6ffe47d
Switch context for hypotheses collector (#3737)
dylanratcliffe Jan 30, 2026
1f42d95
Added links for templates (#3751)
dylanratcliffe Feb 1, 2026
39d9e1b
maint, sources rename cacheField to cache (#3723)
tphoney Feb 2, 2026
0dedf26
Added regional adapter for instance template managers. (#3755)
dylanratcliffe Feb 2, 2026
bd9d3d6
Distill all impersonationHTTPClients into a utility method (#3709)
tphoney Feb 2, 2026
93ab883
Eng 2362 minimize the amount of data in the timeline (#3754)
dylanratcliffe Feb 3, 2026
eeb9134
maint, sources centralise health http server setup (#3764)
tphoney Feb 3, 2026
cfc17c4
maint, api-server refactor into service/area51 (#3761)
tphoney Feb 3, 2026
00b1d52
Eng 2245 review all existing adapters and make sure theyre using the …
Lionel-Wilson Feb 3, 2026
9cf8eac
Eng 2364 create computeproximityplacementgroup adapter (#3774)
Lionel-Wilson Feb 3, 2026
4960edb
Moved CLI JSON output format to ProtoJSON as it's much better (#3767)
dylanratcliffe Feb 3, 2026
1735c89
Run go mod tidy
actions-user Feb 3, 2026
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
58 changes: 29 additions & 29 deletions .terraform.lock.hcl

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

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM ghcr.io/opentofu/opentofu:minimal AS tofu

FROM alpine:3.23.2
FROM alpine:3.23.3

# Copy the tofu binary from the minimal image
COPY --from=tofu /usr/local/bin/tofu /usr/local/bin/tofu
Expand Down
35 changes: 30 additions & 5 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,36 @@ func (flowConfig ClientCredentialsConfig) TokenSource(ctx context.Context, oAuth
return conf.TokenSource(ctx)
}

// Auth0Config contains credentials for creating impersonation HTTP clients
// using Auth0's client credentials flow with account impersonation.
type Auth0Config struct {
Domain string
ClientID string
ClientSecret string
Audience string
}

// ImpersonationHTTPClient creates an HTTP client that can impersonate the specified account.
// If the config is nil or ClientID is empty, returns a basic tracing HTTP client.
func (c *Auth0Config) ImpersonationHTTPClient(ctx context.Context, accountName string) *http.Client {
if c == nil || c.ClientID == "" {
return tracing.HTTPClient()
}
creds := ClientCredentialsConfig{
ClientID: c.ClientID,
ClientSecret: c.ClientSecret,
}
ts := creds.TokenSource(
ctx,
fmt.Sprintf("https://%s/oauth/token", c.Domain),
c.Audience,
WithImpersonateAccount(accountName),
)
// inject otel into oauth2
ctx = context.WithValue(ctx, oauth2.HTTPClient, tracing.HTTPClient())
return oauth2.NewClient(ctx, ts)
}

// natsTokenClient A client that is capable of getting NATS JWTs and signing the
// required nonce to prove ownership of the NKeys. Satisfies the `TokenClient`
// interface
Expand Down Expand Up @@ -161,7 +191,6 @@ func (n *natsTokenClient) generateJWT(ctx context.Context) error {
// If we don't yet have keys generate them
if n.keys == nil {
err := n.generateKeys()

if err != nil {
return err
}
Expand Down Expand Up @@ -257,7 +286,6 @@ func (n *natsTokenClient) GetJWT() (string, error) {
func (n *natsTokenClient) Sign(in []byte) ([]byte, error) {
if n.keys == nil {
err := n.generateKeys()

if err != nil {
return []byte{}, err
}
Expand Down Expand Up @@ -303,7 +331,6 @@ func (ats *APIKeyTokenSource) Token() (*oauth2.Token, error) {
res, err := ats.apiKeyClient.ExchangeKeyForToken(context.Background(), connect.NewRequest(&sdp.ExchangeKeyForTokenRequest{
ApiKey: ats.ApiKey,
}))

if err != nil {
return nil, fmt.Errorf("error exchanging API key: %w", err)
}
Expand All @@ -314,15 +341,13 @@ func (ats *APIKeyTokenSource) Token() (*oauth2.Token, error) {

// Parse the expiry out of the token
token, err := josejwt.ParseSigned(res.Msg.GetAccessToken(), []jose.SignatureAlgorithm{jose.RS256})

if err != nil {
return nil, fmt.Errorf("error parsing JWT: %w", err)
}

claims := josejwt.Claims{}

err = token.UnsafeClaimsWithoutVerification(&claims)

if err != nil {
return nil, fmt.Errorf("error parsing JWT claims: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ type UserTokenContextKey struct{}
// This will be the auth0 `user_id` from the tokens `sub` claim.
type CurrentSubjectContextKey struct{}

// AuthConfig Configuration for the auth middleware
type AuthConfig struct {
// MiddlewareConfig Configuration for the auth middleware
type MiddlewareConfig struct {
Auth0Domain string
Auth0Audience string
// The names of the cookies that will be used to authenticate, these will be
Expand Down Expand Up @@ -169,7 +169,7 @@ func ExtractAccount(ctx context.Context) (string, error) {
// therefore the following environment variables must be set: AUTH0_DOMAIN,
// AUTH0_AUDIENCE. If cookie auth is intended to be used, then AUTH_COOKIE_NAME
// must also be set.
func NewAuthMiddleware(config AuthConfig, next http.Handler) http.Handler {
func NewAuthMiddleware(config MiddlewareConfig, next http.Handler) http.Handler {
processOverrides := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
options := []OverrideAuthOptionFunc{}

Expand Down Expand Up @@ -281,7 +281,7 @@ func withCustomClaims(modify func(*CustomClaims)) OverrideAuthOptionFunc {
//
// This middleware also extract custom claims form the token and stores them in
// CustomClaimsContextKey
func ensureValidTokenHandler(config AuthConfig, next http.Handler) http.Handler {
func ensureValidTokenHandler(config MiddlewareConfig, next http.Handler) http.Handler {
if config.Auth0Domain == "" && config.IssuerURL == "" && config.Auth0Audience == "" {
log.Fatalf("Auth0 configuration is missing")
}
Expand Down
20 changes: 10 additions & 10 deletions auth/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ func TestNewAuthMiddleware(t *testing.T) {

jwksURL := server.Start(ctx)

defaultConfig := AuthConfig{
defaultConfig := MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
}

bypassHealthConfig := AuthConfig{
bypassHealthConfig := MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
BypassAuthForPaths: regexp.MustCompile("/health"),
Expand All @@ -145,7 +145,7 @@ func TestNewAuthMiddleware(t *testing.T) {
Name string
TokenOptions *TestTokenOptions
ExpectedCode int
AuthConfig AuthConfig
AuthConfig MiddlewareConfig
Path string
}{
{
Expand Down Expand Up @@ -263,7 +263,7 @@ func TestNewAuthMiddleware(t *testing.T) {
Scope: "test:pass",
},
},
AuthConfig: AuthConfig{
AuthConfig: MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
BypassAuthForPaths: regexp.MustCompile("/health"),
Expand Down Expand Up @@ -322,7 +322,7 @@ func TestNewAuthMiddleware(t *testing.T) {
},
},
ExpectedCode: http.StatusOK,
AuthConfig: AuthConfig{
AuthConfig: MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
BypassAuth: true,
Expand All @@ -340,7 +340,7 @@ func TestNewAuthMiddleware(t *testing.T) {
},
},
ExpectedCode: http.StatusOK,
AuthConfig: AuthConfig{
AuthConfig: MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
BypassAuth: true,
Expand All @@ -358,7 +358,7 @@ func TestNewAuthMiddleware(t *testing.T) {
},
},
ExpectedCode: http.StatusOK,
AuthConfig: AuthConfig{
AuthConfig: MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
AccountOverride: &correctAccount,
Expand All @@ -376,7 +376,7 @@ func TestNewAuthMiddleware(t *testing.T) {
},
},
ExpectedCode: http.StatusOK,
AuthConfig: AuthConfig{
AuthConfig: MiddlewareConfig{
IssuerURL: jwksURL,
Auth0Audience: "https://api.overmind.tech",
ScopeOverride: &correctScope,
Expand Down Expand Up @@ -536,7 +536,7 @@ func TestOverrideAuth(t *testing.T) {
}

func BenchmarkAuthMiddleware(b *testing.B) {
config := AuthConfig{
config := MiddlewareConfig{
Auth0Domain: "auth.overmind-demo.com",
Auth0Audience: "https://api.overmind.tech",
}
Expand Down Expand Up @@ -705,7 +705,7 @@ func TestConnectErrorHandling(t *testing.T) {
jwksURL := server.Start(ctx)

// Create the middleware
handler := NewAuthMiddleware(AuthConfig{
handler := NewAuthMiddleware(MiddlewareConfig{
Auth0Domain: "",
Auth0Audience: "test",
IssuerURL: jwksURL,
Expand Down
30 changes: 1 addition & 29 deletions aws-source/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ package cmd
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/getsentry/sentry-go"
"github.com/overmindtech/cli/aws-source/proc"
Expand Down Expand Up @@ -89,33 +87,7 @@ var rootCmd = &cobra.Command{
log.WithError(err).Fatal("Could not initialize AWS source")
}

// Start HTTP server for health checks
// Liveness: Check only engine initialization (NATS, heartbeats)
http.HandleFunc("/healthz/alive", e.LivenessProbeHandlerFunc())
// Readiness: Check if adapters are healthy and ready to handle requests
http.HandleFunc("/healthz/ready", e.ReadinessProbeHandlerFunc())
// Backward compatibility - maps to liveness check (matches old behavior)
http.HandleFunc("/healthz", e.LivenessProbeHandlerFunc())

log.WithFields(log.Fields{
"port": healthCheckPort,
}).Debug("Starting healthcheck server with endpoints: /healthz/alive, /healthz/ready, /healthz")

go func() {
defer sentry.Recover()

server := &http.Server{
Addr: fmt.Sprintf(":%v", healthCheckPort),
Handler: nil,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
err := server.ListenAndServe()

log.WithError(err).WithFields(log.Fields{
"port": healthCheckPort,
}).Error("Could not start HTTP server for health checks")
}()
e.ServeHealthProbes(healthCheckPort)

err = e.Start(ctx)
if err != nil {
Expand Down
34 changes: 17 additions & 17 deletions cmd/changes_submit_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,30 +90,30 @@ func tryLoadText(ctx context.Context, fileName string) string {
return strings.TrimSpace(string(bytes))
}

func createBlastRadiusConfig(maxDepth, maxItems int32, maxTime, changeAnalysisMaxTimeout time.Duration) (*sdp.BlastRadiusConfig, error) {
func createBlastRadiusConfig(maxDepth, maxItems int32, maxTime, changeAnalysisTargetDuration time.Duration) (*sdp.BlastRadiusConfig, error) {
var blastRadiusConfigOverride *sdp.BlastRadiusConfig
if maxDepth > 0 || maxItems > 0 || maxTime > 0 || changeAnalysisMaxTimeout > 0 {
if maxDepth > 0 || maxItems > 0 || maxTime > 0 || changeAnalysisTargetDuration > 0 {
blastRadiusConfigOverride = &sdp.BlastRadiusConfig{
MaxItems: maxItems,
LinkDepth: maxDepth,
}
// this is for backward compatibility, remove in a future release
if maxTime > 0 {
// we convert the maxTime to changeAnalysisMaxTimeout, this means multiplying the (blast radius calculation timeout) maxTime by 1.5
// eg 10 minute max (blast radius calculation) -> 15 minute changeanalysis max timeout
blastRadiusConfigOverride.ChangeAnalysisMaxTimeout = durationpb.New(time.Duration(float64(maxTime) * 1.5))
// we convert the maxTime to changeAnalysisTargetDuration, this means multiplying the (blast radius calculation timeout) maxTime by 1.5
// eg 10 minute max (blast radius calculation) -> 15 minute target duration
blastRadiusConfigOverride.ChangeAnalysisTargetDuration = durationpb.New(time.Duration(float64(maxTime) * 1.5))
}
// Add changeAnalysisMaxTimeout if specified
if changeAnalysisMaxTimeout > 0 {
blastRadiusConfigOverride.ChangeAnalysisMaxTimeout = durationpb.New(changeAnalysisMaxTimeout)
// Add changeAnalysisTargetDuration if specified
if changeAnalysisTargetDuration > 0 {
blastRadiusConfigOverride.ChangeAnalysisTargetDuration = durationpb.New(changeAnalysisTargetDuration)
}
}

// validate the ChangeAnalysisMaxTimeout
if blastRadiusConfigOverride != nil && blastRadiusConfigOverride.GetChangeAnalysisMaxTimeout() != nil {
changeAnalysisMaxTimeout = blastRadiusConfigOverride.GetChangeAnalysisMaxTimeout().AsDuration()
if changeAnalysisMaxTimeout < 1*time.Minute || changeAnalysisMaxTimeout > 30*time.Minute {
return nil, flagError{"--change-analysis-max-timeout must be between 1 minute and 30 minutes"}
// validate the ChangeAnalysisTargetDuration
if blastRadiusConfigOverride != nil && blastRadiusConfigOverride.GetChangeAnalysisTargetDuration() != nil {
changeAnalysisTargetDuration = blastRadiusConfigOverride.GetChangeAnalysisTargetDuration().AsDuration()
if changeAnalysisTargetDuration < 1*time.Minute || changeAnalysisTargetDuration > 30*time.Minute {
return nil, flagError{"--change-analysis-target-duration must be between 1 minute and 30 minutes"}
}
}

Expand Down Expand Up @@ -261,9 +261,9 @@ func SubmitPlan(cmd *cobra.Command, args []string) error {
maxDepth := viper.GetInt32("blast-radius-link-depth")
maxItems := viper.GetInt32("blast-radius-max-items")
maxTime := viper.GetDuration("blast-radius-max-time")
changeAnalysisMaxTimeout := viper.GetDuration("change-analysis-max-timeout")
changeAnalysisTargetDuration := viper.GetDuration("change-analysis-target-duration")

blastRadiusConfigOverride, err := createBlastRadiusConfig(maxDepth, maxItems, maxTime, changeAnalysisMaxTimeout)
blastRadiusConfigOverride, err := createBlastRadiusConfig(maxDepth, maxItems, maxTime, changeAnalysisTargetDuration)
if err != nil {
return err
}
Expand Down Expand Up @@ -394,8 +394,8 @@ func init() {
submitPlanCmd.PersistentFlags().Int32("blast-radius-max-items", 0, "Used in combination with '--blast-radius-link-depth' to customise how many items are included in the blast radius. Larger numbers will result in a more comprehensive blast radius, but may take longer to calculate. Defaults to the account level settings.")

submitPlanCmd.PersistentFlags().Duration("blast-radius-max-time", 0, "Maximum time duration for blast radius calculation (e.g., '5m', '15m', '30m'). When the time limit is reached, the analysis continues with risks identified up to that point. Defaults to the account level settings (QUICK: 10m, DETAILED: 15m, FULL: 30m). Valid range: 1m to 30m.")
_ = submitPlanCmd.PersistentFlags().MarkDeprecated("blast-radius-max-time", "This flag is no longer used and will be removed in a future release. Use the '--change-analysis-max-timeout' flag instead.")
submitPlanCmd.PersistentFlags().Duration("change-analysis-max-timeout", 0, "Maximum time duration for change analysis (e.g., '5m', '15m', '30m'). When the time limit is reached, the analysis continues with risks identified up to that point. Defaults to the account level settings (QUICK: 10m, DETAILED: 15m, FULL: 30m). Valid range: 1m to 30m.")
_ = submitPlanCmd.PersistentFlags().MarkDeprecated("blast-radius-max-time", "This flag is no longer used and will be removed in a future release. Use the '--change-analysis-target-duration' flag instead.")
submitPlanCmd.PersistentFlags().Duration("change-analysis-target-duration", 0, "Target duration for change analysis planning (e.g., '5m', '15m', '30m'). This is NOT a hard deadline - the blast radius phase uses 67% of this target to stop gracefully. The job can run slightly past this target and is only hard-stopped at 30 minutes. Defaults to the account level settings (QUICK: 10m, DETAILED: 15m, FULL: 30m). Valid range: 1m to 30m.")
submitPlanCmd.PersistentFlags().String("auto-tag-rules", "", "The path to the auto-tag rules file. If not provided, it will check the default location which is '.overmind/auto-tag-rules.yaml'. If no rules are found locally, the rules configured through the UI are used.")
submitPlanCmd.PersistentFlags().String("signal-config", "", "The path to the signal config file. If not provided, it will check the default location which is '.overmind/signal-config.yaml'. If no config is found locally, the config configured through the UI is used.")
}
Loading