Skip to content

Commit

Permalink
Merge pull request #218 from anchore/debug-logs-readme
Browse files Browse the repository at this point in the history
fix: account routing updates
  • Loading branch information
bradleyjones authored May 17, 2024
2 parents 4da528e + 5c1a1e3 commit 63e3e0f
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 7 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,54 @@ namespace-selectors:
ignore-empty: false
```

### Account Routing

The following configuration options can determine which Anchore account
inventory reports are sent to. Without any of the following configuration the
account set in the `anchore` section will be used.

If a mixture of static account routing and account routing by namespace label
is used then the static account routes configured in k8s-inventory config will
take precedence over any account that is specified by namespace label.

#### Static account routing config

Set a list of accounts and which namespaces inventory should be sent to that
account. You can override the default credentials on a per account basis, if
not set then the global credentials set in the `anchore` section will be used.

```yaml
account-routes:
# Example
# account: # (this is the name of the anchore account e.g. admin)
# user: username
# password: password
# namespaces: # Can be a list of explicit namespaces matches or regex patterns
# - default
# - ^kube-*
```

#### Account routing by namespace label

In this mode use a label set on a kubernetes namespace to determine which
Anchore account inventory data for that namespace should be sent to. It is
assumed that the credentials set in the `anchore` section can post to all
accounts.

```yaml
# Route namespaces to anchore accounts by a label on the namespace
account-route-by-namespace-label:
# The name of the namespace label that will be used to route the contents of
# that namespace to the Anchore account matching the value of the label
key: # e.g anchore.io/account.name
# The name of the account to route inventory to for a namespace that is
# missing the label or if the anchore account is not found.
# If not set then it will default to the account specified in the anchore credentials
default-account: # e.g. admin
# If true will exclude inventorying namespaces that are missing the specified label
ignore-namespace-missing-label: false
```

### Kubernetes API Parameters

This section will allow users to tune the way anchore-k8s-inventory interacts with the kubernetes API server.
Expand Down Expand Up @@ -355,6 +403,7 @@ anchore:
url: <your anchore api url>
user: <anchore-k8s-inventory_inventory_user>
password: $ANCHORE_K8S_INVENTORY_ANCHORE_PASSWORD
account: <anchore account to send inventory reports>
http:
insecure: true
timeout-seconds: 10
Expand Down
4 changes: 3 additions & 1 deletion anchore-k8s-inventory.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ account-route-by-namespace-label:
# The name of the namespace label that will be used to route the contents of
# that namespace to the Anchore account matching the value of the label
key: # e.g anchore.io/account.name
# The name of the account to route inventory to for a namespace that is missing the label
# The name of the account to route inventory to for a namespace that is
# missing the label or if the anchore account is not found.
# If not set then it will default to the account specified in the anchore credentials
default-account: # e.g. admin
# If true will exclude inventorying namespaces that are missing the specified label
Expand Down Expand Up @@ -113,6 +114,7 @@ anchore:
# url: $ANCHORE_K8S_INVENTORY_ANCHORE_URL
# user: $ANCHORE_K8S_INVENTORY_ANCHORE_USER
password: $ANCHORE_K8S_INVENTORY_ANCHORE_PASSWORD
# account: admin
# http:
# insecure: true
# timeout-seconds: 10
17 changes: 16 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package cmd

import (
"errors"
"fmt"
"os"
"runtime/pprof"

"github.com/anchore/k8s-inventory/pkg/mode"
"github.com/anchore/k8s-inventory/pkg/reporter"

"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -55,13 +57,26 @@ var rootCmd = &cobra.Command{
log.Errorf("Failed to get Image Results: %+v", err)
os.Exit(1)
}
anErrorOccurred := false
for account, report := range reports {
err = pkg.HandleReport(report, appConfig, account)
if errors.Is(err, reporter.ErrAnchoreAccountDoesNotExist) {
// Retry with default account
retryAccount := appConfig.AnchoreDetails.Account
if appConfig.AccountRouteByNamespaceLabel.DefaultAccount != "" {
retryAccount = appConfig.AccountRouteByNamespaceLabel.DefaultAccount
}
log.Warnf("Anchore Account %s does not exist, sending to default account", account)
err = pkg.HandleReport(report, appConfig, retryAccount)
}
if err != nil {
log.Errorf("Failed to handle Image Results: %+v", err)
os.Exit(1)
anErrorOccurred = true
}
}
if anErrorOccurred {
os.Exit(1)
}
}
},
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/inventory/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ func FetchNamespaces(
// Only return namespaces that are explicitly included if set
if len(includes) > 0 {
for _, ns := range includes {
nsList = append(nsList, nsMap[ns])
if _, exists := nsMap[ns]; exists {
nsList = append(nsList, nsMap[ns])
}
}
return nsList, nil
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ k8s go SDK

import (
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
Expand Down Expand Up @@ -75,6 +76,9 @@ func HandleReport(report inventory.Report, cfg *config.Application, account stri

if anchoreDetails.IsValid() {
if err := reporter.Post(report, anchoreDetails); err != nil {
if errors.Is(err, reporter.ErrAnchoreAccountDoesNotExist) {
return err
}
return fmt.Errorf("unable to report Inventory to Anchore account %s: %w", account, err)
}
log.Infof("Inventory report sent to Anchore account %s", account)
Expand All @@ -97,6 +101,15 @@ func PeriodicallyGetInventoryReport(cfg *config.Application) {
} else {
for account, report := range reports {
err := HandleReport(report, cfg, account)
if errors.Is(err, reporter.ErrAnchoreAccountDoesNotExist) {
// Retry with default account
retryAccount := cfg.AnchoreDetails.Account
if cfg.AccountRouteByNamespaceLabel.DefaultAccount != "" {
retryAccount = cfg.AccountRouteByNamespaceLabel.DefaultAccount
}
log.Warnf("Anchore Account %s does not exist, sending to default account", account)
err = HandleReport(report, cfg, retryAccount)
}
if err != nil {
log.Errorf("Failed to handle Inventory Report: %w", err)
}
Expand Down Expand Up @@ -274,12 +287,14 @@ func GetAccountRoutedNamespaces(defaultAccount string, namespaces []inventory.Na
for _, ns := range namespaces {
for _, namespaceRegex := range route.Namespaces {
if regexp.MustCompile(namespaceRegex).MatchString(ns.Name) {
log.Debugf("Namespace %s matched route from config %s", ns.Name, routeNS)
accountNamespaces[ns.Name] = struct{}{}
accountRoutesForAllNamespaces[routeNS] = append(accountRoutesForAllNamespaces[routeNS], ns)
}
}
}
}

// If there is a namespace label routing, add namespaces to the account routes based on the label,
// if the namespace has not already been added to an account route set via explicit configuration in
// accountRoutes config. (This overrides the label routing for the case where the label cannot be changed).
Expand All @@ -288,12 +303,16 @@ func GetAccountRoutedNamespaces(defaultAccount string, namespaces []inventory.Na
_, namespaceRouted := accountNamespaces[ns.Name]
if namespaceLabelRouting.LabelKey != "" && !namespaceRouted {
if account, ok := ns.Labels[namespaceLabelRouting.LabelKey]; ok {
log.Debugf("Namespace %s matched route from label %s", ns.Name, account)
accountRoutesForAllNamespaces[account] = append(accountRoutesForAllNamespaces[account], ns)
} else if !namespaceLabelRouting.IgnoreMissingLabel {
accountRoutesForAllNamespaces[defaultAccount] = append(accountRoutesForAllNamespaces[defaultAccount], ns)
} else {
log.Infof("Ignoring namespace %s because it does not have the label %s", ns.Name, namespaceLabelRouting.LabelKey)
}
} else if !namespaceRouted {
accountRoutesForAllNamespaces[defaultAccount] = append(accountRoutesForAllNamespaces[defaultAccount], ns)
log.Debugf("Namespace %s added to default account %s", ns.Name, defaultAccount)
}
}

Expand Down
36 changes: 32 additions & 4 deletions pkg/reporter/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/anchore/k8s-inventory/internal/config"
Expand All @@ -19,13 +20,26 @@ import (
)

const (
reportAPIPathV1 = "v1/enterprise/kubernetes-inventory"
reportAPIPathV2 = "v2/kubernetes-inventory"
reportAPIPathV1 = "v1/enterprise/kubernetes-inventory"
reportAPIPathV2 = "v2/kubernetes-inventory"
AnchoreAccountMissingError = "User account not found"
)

var enterpriseEndpoint = reportAPIPathV2
var (
ErrAnchoreAccountDoesNotExist = fmt.Errorf("user account not found")
enterpriseEndpoint = reportAPIPathV2
)

type AnchoreResponse struct {
Message string `json:"message"`
Httpcode int `json:"httpcode"`
Detail interface{} `json:"detail"`
AnchoreRequestID string `json:"anchore_request_id"`
}

// This method does the actual Reporting (via HTTP) to Anchore
//
//nolint:funlen
func Post(report inventory.Report, anchoreDetails config.AnchoreInfo) error {
defer tracker.TrackFunctionTime(time.Now(), "Reporting results to Anchore for cluster: "+report.ClusterName+"")
log.Debug("Validating and normalizing report before sending to Anchore")
Expand Down Expand Up @@ -78,7 +92,21 @@ func Post(report inventory.Report, anchoreDetails config.AnchoreInfo) error {
log.Info("Retrying inventory report with new endpoint: ", enterpriseEndpoint)
return Post(report, anchoreDetails)
}
return fmt.Errorf("failed to report data to Anchore: %+v", resp)

// Check if account is correct
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response from Anchore: %w", err)
}
anchoreResponse := &AnchoreResponse{}
err = json.Unmarshal(respBody, anchoreResponse)
if err != nil {
return fmt.Errorf("failed to parse response from Anchore: %w", err)
}
if strings.Contains(anchoreResponse.Message, AnchoreAccountMissingError) {
return ErrAnchoreAccountDoesNotExist
}
return fmt.Errorf("failed to report data to Anchore: %s", string(respBody))
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("failed to report data to Anchore: %+v", resp)
Expand Down

0 comments on commit 63e3e0f

Please sign in to comment.