Skip to content

Commit 5b3973e

Browse files
committed
fix: gate GCP worload ID cleanup
release v2.3.0 introduced gcp workload identity but included a regression. if you weren't on GCP it would try to check for a CR for which the CRD was absent. in this commit we plumb through the helm conditional used for the RBAC into the runtime and use it to gate all GCP WI related steps.
1 parent 26e6021 commit 5b3973e

7 files changed

Lines changed: 86 additions & 55 deletions

File tree

charts/restate-operator-helm/templates/deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ spec:
6060
- name: AWS_POD_IDENTITY_ASSOCIATION_CLUSTER
6161
value: {{ .Values.awsPodIdentityAssociationCluster }}
6262
{{- end }}
63+
{{- if .Values.gcpWorkloadIdentity }}
64+
- name: GCP_WORKLOAD_IDENTITY
65+
value: "true"
66+
{{- end }}
6367
{{- if .Values.clusterDns }}
6468
- name: CLUSTER_DNS
6569
value: {{ .Values.clusterDns }}

charts/restate-operator-helm/templates/rbac.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ rules:
103103
apiGroups:
104104
- eks.services.k8s.aws
105105
{{- end }}
106-
{{- if .Values.gcpConfigConnector }}
106+
{{- if .Values.gcpWorkloadIdentity }}
107107
- resources:
108108
- iampolicymembers
109109
verbs:

charts/restate-operator-helm/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ serviceAccount:
1414
podAnnotations: {}
1515

1616
awsPodIdentityAssociationCluster: null
17-
gcpConfigConnector: null
17+
gcpWorkloadIdentity: null
1818
clusterDns: null # defaults to "cluster.local" in the operator binary
1919

2020
podSecurityContext:

src/controllers/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub struct State {
3838
pub registry: prometheus::Registry,
3939
/// If set, watch AWS PodIdentityAssociation resources, and if requested create them against this cluster
4040
aws_pod_identity_association_cluster: Option<String>,
41+
/// If true, manage GCP Workload Identity via Config Connector IAMPolicyMember
42+
gcp_workload_identity: bool,
4143

4244
/// Our namespace, needed for network policies and reading secrets
4345
operator_namespace: String,
@@ -57,6 +59,7 @@ pub struct State {
5759
impl State {
5860
pub fn new(
5961
aws_pod_identity_association_cluster: Option<String>,
62+
gcp_workload_identity: bool,
6063
operator_namespace: String,
6164
operator_label_name: Option<String>,
6265
operator_label_value: Option<String>,
@@ -67,6 +70,7 @@ impl State {
6770
diagnostics: Arc::new(RwLock::new(Diagnostics::default())),
6871
registry: prometheus::Registry::default(),
6972
aws_pod_identity_association_cluster,
73+
gcp_workload_identity,
7074
operator_namespace,
7175
operator_label_name,
7276
operator_label_value,

src/controllers/restatecluster/controller.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ pub(super) struct Context {
7272
pub security_group_policy_installed: bool,
7373
// Whether the SecretProviderClass CRD is installed
7474
pub secret_provider_class_installed: bool,
75+
// Whether GCP Workload Identity management is enabled
76+
pub gcp_workload_identity: bool,
7577
/// The cluster DNS suffix (e.g. "cluster.local")
7678
pub cluster_dns: String,
7779
/// Diagnostics read by the web server
@@ -81,6 +83,7 @@ pub(super) struct Context {
8183
}
8284

8385
impl Context {
86+
#[allow(clippy::too_many_arguments)]
8487
pub fn new(
8588
client: Client,
8689
metrics: Metrics,
@@ -103,6 +106,7 @@ impl Context {
103106
operator_label_value: state.operator_label_value.clone(),
104107
security_group_policy_installed,
105108
secret_provider_class_installed,
109+
gcp_workload_identity: state.gcp_workload_identity,
106110
cluster_dns: state.cluster_dns.clone(),
107111
diagnostics: state.diagnostics.clone(),
108112
metrics,
@@ -555,7 +559,7 @@ pub async fn run(client: Client, metrics: Metrics, state: State) {
555559
} else {
556560
controller
557561
};
558-
let controller = if iam_policy_member_installed {
562+
let controller = if state.gcp_workload_identity && iam_policy_member_installed {
559563
let ipm_watcher = watcher(ipm_api, cfg.clone())
560564
.map(ensure_deletion_change)
561565
.touched_objects()

src/controllers/restatecluster/reconcilers/compute.rs

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -624,63 +624,73 @@ pub async fn reconcile_compute(
624624
.and_then(|s| s.service_account_annotations.as_ref())
625625
.and_then(|a| a.get("iam.gke.io/gcp-service-account"));
626626

627-
if let Some(gcp_sa_email) = gcp_sa_email {
628-
let gcp_project = match parse_gcp_project_from_sa_email(gcp_sa_email) {
629-
Some(project) => project,
630-
None => {
631-
return Err(Error::InvalidRestateConfig(format!(
632-
"Cannot parse GCP project from service account email '{gcp_sa_email}'; expected format: name@PROJECT.iam.gserviceaccount.com"
633-
)));
634-
}
635-
};
636-
637-
let ipm = match apply_iam_policy_member(
638-
name,
639-
&ipm_api,
640-
restate_iam_policy_member(name, base_metadata, gcp_project, gcp_sa_email),
641-
)
642-
.await
643-
{
644-
Ok(ipm) => ipm,
645-
Err(Error::KubeError(kube::Error::Api(kube::error::ErrorResponse {
646-
code: 404,
647-
..
648-
}))) => {
627+
match (ctx.gcp_workload_identity, gcp_sa_email) {
628+
(true, Some(gcp_sa_email)) => {
629+
let gcp_project = match parse_gcp_project_from_sa_email(gcp_sa_email) {
630+
Some(project) => project,
631+
None => {
632+
return Err(Error::InvalidRestateConfig(format!(
633+
"Cannot parse GCP project from service account email '{gcp_sa_email}'; expected format: name@PROJECT.iam.gserviceaccount.com"
634+
)));
635+
}
636+
};
637+
638+
let ipm = match apply_iam_policy_member(
639+
name,
640+
&ipm_api,
641+
restate_iam_policy_member(name, base_metadata, gcp_project, gcp_sa_email),
642+
)
643+
.await
644+
{
645+
Ok(ipm) => ipm,
646+
Err(Error::KubeError(kube::Error::Api(kube::error::ErrorResponse {
647+
code: 404,
648+
..
649+
}))) => {
650+
return Err(Error::NotReady {
651+
reason: "IAMPolicyMemberCRDNotFound".into(),
652+
message: "IAMPolicyMember CRD not found - is Config Connector installed?"
653+
.into(),
654+
requeue_after: Some(Duration::from_secs(60)),
655+
});
656+
}
657+
Err(err) => return Err(err),
658+
};
659+
660+
if !is_iam_policy_member_ready(&ipm) {
649661
return Err(Error::NotReady {
650-
reason: "IAMPolicyMemberCRDNotFound".into(),
651-
message: "IAMPolicyMember CRD not found - is Config Connector installed?"
652-
.into(),
653-
requeue_after: Some(Duration::from_secs(60)),
662+
reason: "IAMPolicyMemberNotReady".into(),
663+
message:
664+
"Waiting for Config Connector to provision the IAM policy member binding"
665+
.into(),
666+
requeue_after: None,
654667
});
655668
}
656-
Err(err) => return Err(err),
657-
};
658669

659-
if !is_iam_policy_member_ready(&ipm) {
660-
return Err(Error::NotReady {
661-
reason: "IAMPolicyMemberNotReady".into(),
662-
message: "Waiting for Config Connector to provision the IAM policy member binding"
663-
.into(),
664-
requeue_after: None,
665-
});
666-
}
667-
668-
check_workload_identity(
669-
name,
670-
base_metadata,
671-
spec.compute.tolerations.as_ref(),
672-
&job_api,
673-
)
674-
.await?;
670+
check_workload_identity(
671+
name,
672+
base_metadata,
673+
spec.compute.tolerations.as_ref(),
674+
&job_api,
675+
)
676+
.await?;
675677

676-
let pod_annotations = pod_annotations.get_or_insert_with(Default::default);
677-
pod_annotations.insert(
678-
"restate.dev/gcp-service-account".into(),
679-
gcp_sa_email.to_owned(),
680-
);
681-
} else {
682-
delete_iam_policy_member(name, &ipm_api, "restate-workload-identity").await?;
683-
delete_job(name, &job_api, "restate-wi-canary").await?;
678+
let pod_annotations = pod_annotations.get_or_insert_with(Default::default);
679+
pod_annotations.insert(
680+
"restate.dev/gcp-service-account".into(),
681+
gcp_sa_email.to_owned(),
682+
);
683+
}
684+
(true, None) => {
685+
delete_iam_policy_member(name, &ipm_api, "restate-workload-identity").await?;
686+
delete_job(name, &job_api, "restate-wi-canary").await?;
687+
}
688+
(false, Some(gcp_sa_email)) => {
689+
warn!(
690+
"Ignoring iam.gke.io/gcp-service-account annotation {gcp_sa_email} as the operator is not configured with --gcp-workload-identity"
691+
);
692+
}
693+
(false, None) => {}
684694
}
685695

686696
let restate_service = restate_service(

src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ struct Arguments {
1717
)]
1818
aws_pod_identity_association_cluster: Option<String>,
1919

20+
#[arg(
21+
long = "gcp-workload-identity",
22+
env = "GCP_WORKLOAD_IDENTITY",
23+
value_name = "ENABLED",
24+
default_value = "false"
25+
)]
26+
gcp_workload_identity: bool,
27+
2028
#[arg(
2129
long = "operator-namespace",
2230
env = "OPERATOR_NAMESPACE",
@@ -95,6 +103,7 @@ async fn main() -> anyhow::Result<()> {
95103
// Initialize Kubernetes controller state
96104
let state = State::new(
97105
args.aws_pod_identity_association_cluster,
106+
args.gcp_workload_identity,
98107
args.operator_namespace,
99108
args.operator_label_name,
100109
args.operator_label_value,

0 commit comments

Comments
 (0)