Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export LOGGING_VERSION?=6.3
export VERSION=$(LOGGING_VERSION).0
export NAMESPACE?=openshift-logging

IMAGE_LOGGING_VECTOR?=quay.io/openshift-logging/vector:6.1
IMAGE_LOGGING_VECTOR?=quay.io/openshift-logging/vector:v0.37.1
IMAGE_LOGFILEMETRICEXPORTER?=quay.io/openshift-logging/log-file-metric-exporter:6.1
IMAGE_LOGGING_EVENTROUTER?=quay.io/openshift-logging/eventrouter:0.3

Expand Down
6 changes: 3 additions & 3 deletions bundle/manifests/cluster-logging.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ metadata:
categories: OpenShift Optional, Logging & Tracing
certified: "false"
containerImage: quay.io/openshift-logging/cluster-logging-operator:latest
createdAt: "2025-04-07T20:20:21Z"
createdAt: "2025-04-08T15:35:39Z"
description: The Red Hat OpenShift Logging Operator for OCP provides a means for
configuring and managing log collection and forwarding.
features.operators.openshift.io/cnf: "false"
Expand Down Expand Up @@ -2056,7 +2056,7 @@ spec:
- name: OPERATOR_NAME
value: cluster-logging-operator
- name: RELATED_IMAGE_VECTOR
value: quay.io/openshift-logging/vector:6.1
value: quay.io/openshift-logging/vector:v0.37.1
- name: RELATED_IMAGE_LOG_FILE_METRIC_EXPORTER
value: quay.io/openshift-logging/log-file-metric-exporter:6.1
image: quay.io/openshift-logging/cluster-logging-operator:latest
Expand Down Expand Up @@ -2103,7 +2103,7 @@ spec:
provider:
name: Red Hat
relatedImages:
- image: quay.io/openshift-logging/vector:6.1
- image: quay.io/openshift-logging/vector:v0.37.1
name: vector
- image: quay.io/openshift-logging/log-file-metric-exporter:6.1
name: log-file-metric-exporter
Expand Down
2 changes: 1 addition & 1 deletion config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ spec:
- name: OPERATOR_NAME
value: "cluster-logging-operator"
- name: RELATED_IMAGE_VECTOR
value: quay.io/openshift-logging/vector:6.1
value: quay.io/openshift-logging/vector:v0.37.1
- name: RELATED_IMAGE_LOG_FILE_METRIC_EXPORTER
value: quay.io/openshift-logging/log-file-metric-exporter:6.1
164 changes: 164 additions & 0 deletions docs/features/logforwarding/outputs/cloudwatch-sts-forwarding.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
= Forwarding to Amazon Cloudwatch using Web Identities From an STS enabled Cluster

This guide provides a workflow for forwarding to Amazon Cloudwatch in an <<aws-sts, STS>> enabled cluster.

These steps assume that there is a <<setup-sts, STS>> enabled openshift cluster running.

---
== Steps to forward to Cloudwatch using Web Identity

=== Creating a `CredentialsRequest`
. Create a `CredentialsRequest` resource with the appropriate actions.
.. This example `CredentialsRequest` allows creating and describing logs.
+
.aws-cred-request.yaml
[source, yaml]
----
apiVersion: cloudcredential.openshift.io/v1
kind: CredentialsRequest
metadata:
name: my-credrequest # <1>
namespace: openshift-logging <1>
spec:
providerSpec:
apiVersion: cloudcredential.openshift.io/v1
kind: AWSProviderSpec
statementEntries:
- action: # <2>
- logs:PutLogEvents
- logs:CreateLogGroup
- logs:PutRetentionPolicy
- logs:CreateLogStream
- logs:DescribeLogGroups
- logs:DescribeLogStreams
effect: Allow
resource: arn:aws:logs:*:*:*
secretRef:
name: sts-secret # <3>
namespace: openshift-logging # <3>
serviceAccountNames:
- my-sa # <4>
----
<1> The name and namespace for the credentials request.
<2> The allowed actions for this role.
<3> The name and namespace of the secret containing the generated credentials.
<4> The service account(s) that will use the credentials
+

. Create the role in `AWS` using the <<cco, CCO>> utility.
+
```
$ ccoctl aws create-iam-roles --name=<NAME> --region=<REGION> \ # <1>
--credentials-requests-dir=<REQ DIR> \ # <2>
--output-dir=<OUTPUT> \ # <3>
--identity-provider-arn=arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/<NAME>-oidc.s3.<REGION>.amazonaws.com # <4>
```
<1> The name of the resource along with the region
<2> The credentials request directory where the above `CredentialsRequest` YAML is saved.
<3> The output directory
<4> The identity provider arn
+

. Apply the generated credentials secret.
+
.openshift-logging-sts-secret-credentials.yaml
[source, yaml]
----
apiVersion: v1
stringData:
credentials: |-
[default]
sts_regional_endpoints = regional
role_arn = <MY ROLE ARN>
web_identity_token_file = <PATH TO SA TOKEN>
kind: Secret
metadata:
name: sts-secret
namespace: openshift-logging
type: Opaque
----
+
```
$ oc apply -f openshift-logging-sts-secret-credentials.yaml
```


=== Configuring a `ClusterLogForwarder`

This example forwarder shows two ways to configure a `cloudwatch` output, with a projected service account token or using a secret containing the service account token.

. Create a `ClusterLogForwarder.yaml`
+
.cluster-log-forwarder.yaml
[source,yaml]
----
apiVersion: observability.openshift.io/v1
kind: ClusterLogForwarder
metadata:
name: cw-forwarder # <1>
namespace: openshift-logging # <1>
spec:
serviceAccount:
name: my-sa # <2>
outputs:
- name: cw-sa-projected-token
type: cloudwatch
cloudwatch:
groupName: 'cw-projected{.log_type||"missing"}' # <3>
region: us-west-1
authentication:
type: iamRole # <4>
iamRole:
roleARN: # <5>
key: credentials # <5>
secretName: sts-secret # <5>
token: # <6>
from: serviceAccount # <6>
- name: cw-sa-token-secret
type: cloudwatch
cloudwatch:
groupName: 'cw-token-secret{.kubernetes.namespace_name||.log_type||"missing"}'
region: us-west-1
authentication:
type: iamRole
iamRole:
roleARN:
key: role_arn
secretName: foo-sts-secret
token:
from: secret # <7>
key: token # <7>
name: my-sa-token-secret # <7>
pipelines:
- name: app-logs
inputRefs:
- application
outputRefs:
- cw-sa-projected-token
- cw-sa-token-secret
----
<1> The name and namespace of the forwarder
<2> The service account with the appropriate collection permissions
<3> Group name for the log stream. Can be templated.
<4> The authentication type. For `STS`, use `iamRole`.
<5> The `role_arn` used to authenticate. Specify the name of the secret and the key where the `role_arn` is stored.
<6> The service account token used to authenticate. To use the projected service account token, specify `from: serviceAccount`.
<7> To use a token from a secret, specify `from: secret` and provide the key and secret name
+

. Apply the configured forwarder.
+
```
$ oc apply -f cluster-log-forwarder.yaml
```

== References
=== Openshift

. [[setup-sts]] https://github.com/openshift/cloud-credential-operator/blob/master/docs/sts.md[Setting up an STS cluster]
. [[cco]] https://github.com/openshift/cloud-credential-operator[Cloud Credential Operator (CCO)]
. https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/logging/index[Openshift Logging Documentation]

=== Amazon
. [[aws-sts]] https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html[AWS Security Token Service (STS)]
.
57 changes: 0 additions & 57 deletions internal/collector/cloudwatch.go

This file was deleted.

7 changes: 7 additions & 0 deletions internal/collector/cloudwatch/cloudwatch.credentials.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{- range . -}}
[output_{{ .Name }}]
web_identity_token_file={{ .WebIdentityTokenFile }}
role_arn={{ .RoleARN }}
role_session_name=output-{{ .Name }}

{{end -}}
92 changes: 92 additions & 0 deletions internal/collector/cloudwatch/cloudwatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cloudwatch

import (
_ "embed"
"html/template"
"strings"

log "github.com/ViaQ/logerr/v2/log/static"
obs "github.com/openshift/cluster-logging-operator/api/observability/v1"
"github.com/openshift/cluster-logging-operator/internal/api/observability"
"github.com/openshift/cluster-logging-operator/internal/collector/common"
"github.com/openshift/cluster-logging-operator/internal/constants"
"github.com/openshift/cluster-logging-operator/internal/generator/vector/output/cloudwatch"
"github.com/openshift/cluster-logging-operator/internal/reconcile"
"github.com/openshift/cluster-logging-operator/internal/runtime"
"github.com/openshift/cluster-logging-operator/internal/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
CloudwatchCredentialsTemplate = template.Must(template.New("cw credentials").Parse(cloudwatchCredentialsTemplateStr))
//go:embed cloudwatch.credentials.tmpl
cloudwatchCredentialsTemplateStr string
)

type CloudwatchWebIdentity struct {
Name string
RoleARN string
WebIdentityTokenFile string
}

// ReconcileAWSCredentialsConfigMap reconciles a configmap with credential profile(s) for Cloudwatch output(s).
func ReconcileAWSCredentialsConfigMap(k8sClient client.Client, reader client.Reader, namespace, name string, outputs []obs.OutputSpec, secrets observability.Secrets, configMaps map[string]*corev1.ConfigMap, owner metav1.OwnerReference) (*corev1.ConfigMap, error) {
log.V(3).Info("generating AWS ConfigMap")
credString, err := GenerateCloudwatchCredentialProfiles(outputs, secrets)

if err != nil || credString == "" {
return nil, err
}

configMap := runtime.NewConfigMap(
namespace,
name,
map[string]string{
constants.AWSCredentialsKey: credString,
})

utils.AddOwnerRefToObject(configMap, owner)
return configMap, reconcile.Configmap(k8sClient, k8sClient, configMap)
}

// GenerateCloudwatchCredentialProfiles generates AWS CLI profiles for a credentials file from spec'd cloudwatch role ARNs and returns the formatted content as a string.
func GenerateCloudwatchCredentialProfiles(outputs []obs.OutputSpec, secrets observability.Secrets) (string, error) {
// Gather all cloudwatch output's role_arns/tokens
webIds := GatherAWSWebIdentities(outputs, secrets)

// No CW outputs
if webIds == nil {
return "", nil
}

// Execute Go template to generate credential profile(s)
w := &strings.Builder{}
err := CloudwatchCredentialsTemplate.Execute(w, webIds)
if err != nil {
return "", err
}
return w.String(), nil
}

// GatherAWSWebIdentities takes spec'd role arns and generates CloudwatchWebIdentity objects with a name and token path from secret or projected SA token
func GatherAWSWebIdentities(outputs []obs.OutputSpec, secrets observability.Secrets) (webIds []CloudwatchWebIdentity) {
for _, o := range outputs {
if o.Type == obs.OutputTypeCloudwatch && o.Cloudwatch.Authentication != nil && o.Cloudwatch.Authentication.Type == obs.CloudwatchAuthTypeIAMRole {
if roleARN := cloudwatch.ParseRoleArn(o.Cloudwatch.Authentication, secrets); roleARN != "" {
tokenPath := common.ServiceAccountBasePath(constants.TokenKey)
if o.Cloudwatch.Authentication.IAMRole.Token.From == obs.BearerTokenFromSecret {
secret := o.Cloudwatch.Authentication.IAMRole.Token.Secret
tokenPath = common.SecretPath(secret.Name, secret.Key)
}
webIds = append(webIds, CloudwatchWebIdentity{
Name: o.Name,
RoleARN: roleARN,
WebIdentityTokenFile: tokenPath,
})
}
}
}
return webIds
}
Loading