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
5 changes: 4 additions & 1 deletion internal/central/pkg/externaldns/externaldns.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ import "github.com/stackrox/acs-fleet-manager/internal/central/pkg/api/private"
// IsEnabled checks if the external DNS feature is enabled for the given managed central.
func IsEnabled(managedCentral private.ManagedCentral) bool {
isEnabled, ok := managedCentral.Spec.TenantResourcesValues["externalDnsEnabled"].(bool)
return ok && isEnabled
if !ok {
return true // By default, external DNS is enabled
}
return isEnabled
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package centralmgrs

import (
"context"

"github.com/golang/glog"
"github.com/google/uuid"
"github.com/pkg/errors"
Expand All @@ -20,6 +22,7 @@ type CentralRoutesCNAMEManager struct {
centralService services.CentralService
centralConfig *config.CentralConfig
managedCentralPresenter *presenters.ManagedCentralPresenter
uiReachabilityChecker UIReachabilityChecker
}

var _ workers.Worker = &CentralRoutesCNAMEManager{}
Expand All @@ -36,6 +39,7 @@ func NewCentralCNAMEManager(centralService services.CentralService, centralConfi
centralService: centralService,
centralConfig: centralConfig,
managedCentralPresenter: managedCentralPresenter,
uiReachabilityChecker: NewHTTPUIReachabilityChecker(),
}
}

Expand Down Expand Up @@ -67,35 +71,52 @@ func (k *CentralRoutesCNAMEManager) Reconcile() []error {
errs = append(errs, errors.Wrapf(err, "failed to present managed central for central %s", central.ID))
continue
}
if k.centralConfig.EnableCentralExternalDomain && !externaldns.IsEnabled(managedCentral) {
if central.RoutesCreationID == "" {
glog.Infof("creating CNAME records for central %s", central.ID)

changeOutput, err := k.centralService.ChangeCentralCNAMErecords(central, services.CentralRoutesActionUpsert)

if err != nil {
errs = append(errs, err)
continue
if k.centralConfig.EnableCentralExternalDomain {
if !externaldns.IsEnabled(managedCentral) {
if central.RoutesCreationID == "" {
glog.Infof("creating CNAME records for central %s", central.ID)

changeOutput, err := k.centralService.ChangeCentralCNAMErecords(central, services.CentralRoutesActionUpsert)

if err != nil {
errs = append(errs, err)
continue
}

switch {
case changeOutput == nil:
glog.Infof("creating CNAME records failed with nil result")
continue
case changeOutput.ChangeInfo == nil || changeOutput.ChangeInfo.Id == nil || changeOutput.ChangeInfo.Status == "":
glog.Infof("creating CNAME records failed with nil info")
continue
}

central.RoutesCreationID = *changeOutput.ChangeInfo.Id
central.RoutesCreated = changeOutput.ChangeInfo.Status == "INSYNC"
} else {
recordStatus, err := k.centralService.GetCNAMERecordStatus(central)
if err != nil {
errs = append(errs, err)
continue
}
central.RoutesCreated = *recordStatus.Status == "INSYNC"
}

switch {
case changeOutput == nil:
glog.Infof("creating CNAME records failed with nil result")
continue
case changeOutput.ChangeInfo == nil || changeOutput.ChangeInfo.Id == nil || changeOutput.ChangeInfo.Status == "":
glog.Infof("creating CNAME records failed with nil info")
continue
}

central.RoutesCreationID = *changeOutput.ChangeInfo.Id
central.RoutesCreated = changeOutput.ChangeInfo.Status == "INSYNC"
} else {
recordStatus, err := k.centralService.GetCNAMERecordStatus(central)
if err != nil {
errs = append(errs, err)
continue
// External DNS is enabled for this central (managed by external-dns operator)
ctx := context.Background()
uiReachable, checkErr := k.uiReachabilityChecker.IsReachable(ctx, managedCentral.Spec.UiHost)
if checkErr != nil {
glog.Warningf("Failed to check UI reachability for central %s at %s: %v",
central.ID, managedCentral.Spec.UiHost, checkErr)
} else if !uiReachable {
glog.Infof("Central %s UI at %s is not yet reachable from internet",
central.ID, managedCentral.Spec.UiHost)
} else {
glog.Infof("Central %s UI at %s is reachable from internet",
central.ID, managedCentral.Spec.UiHost)
central.RoutesCreated = true
}
central.RoutesCreated = *recordStatus.Status == "INSYNC"
}
} else {
glog.Infof("external certificate is disabled, skip CNAME creation for Central %s", central.ID)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package centralmgrs

import (
"context"
"fmt"
"net/http"
"time"

"github.com/golang/glog"
"github.com/pkg/errors"
)

const (
httpCheckTimeout = 10 * time.Second
)

// UIReachabilityChecker checks if a Central UI is reachable from the internet
type UIReachabilityChecker interface {
IsReachable(ctx context.Context, uiHost string) (bool, error)
}

// HTTPUIReachabilityChecker is the default implementation that performs actual HTTP checks
type HTTPUIReachabilityChecker struct {
httpClient *http.Client
}

// NewHTTPUIReachabilityChecker creates a new HTTP-based reachability checker
func NewHTTPUIReachabilityChecker() *HTTPUIReachabilityChecker {
return &HTTPUIReachabilityChecker{
httpClient: &http.Client{
Timeout: httpCheckTimeout,
// Don't follow redirects automatically, we want to check the exact host
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
}
}

// IsReachable performs an HTTP HEAD request to verify if the Central UI host is reachable
func (c *HTTPUIReachabilityChecker) IsReachable(ctx context.Context, uiHost string) (bool, error) {
if uiHost == "" {
return false, errors.New("UI host is empty")
}

// Construct the URL with https scheme
url := fmt.Sprintf("https://%s", uiHost)

// Create request with context
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false, errors.Wrapf(err, "creating HTTP request for %s", url)
}

// Perform the request
resp, err := c.httpClient.Do(req)
if err != nil {
// Network errors mean the host is not reachable
return false, nil
}
defer func() {
_ = resp.Body.Close()
}()

// Accept any response status code in the 2xx or 3xx range as "reachable"
// This indicates the DNS resolved and the server responded
isSuccess := resp.StatusCode >= 200 && resp.StatusCode < 400
if !isSuccess {
glog.Infof("UI reachability check failed for host %q with status code %d", uiHost, resp.StatusCode)
} else {
glog.Infof("UI reachability check succeeded for host %q with status code %d", uiHost, resp.StatusCode)
}

return isSuccess, nil
}
Loading