Skip to content

Commit a8740eb

Browse files
Resolve merge conflicts
Signed-off-by: Gaziza Yestemirova <[email protected]>
2 parents 45d8ca9 + d13144c commit a8740eb

File tree

75 files changed

+1712
-1639
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1712
-1639
lines changed

.gen/proto/sharddistributor/v1/executor.pb.go

Lines changed: 212 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.gen/proto/sharddistributor/v1/executor.pb.yarpc.go

Lines changed: 47 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/activecluster/manager_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ func TestGetActiveClusterInfoByClusterAttribute(t *testing.T) {
5858
name: "nil cluster attribute - returns domain-level active cluster info",
5959
clusterAttribute: nil,
6060
activeClusterCfg: &types.ActiveClusters{
61-
ActiveClustersByRegion: map[string]types.ActiveClusterInfo{
62-
"us-west": {
63-
ActiveClusterName: "cluster0",
64-
FailoverVersion: 20,
61+
AttributeScopes: map[string]types.ClusterAttributeScope{
62+
"region": {
63+
ClusterAttributes: map[string]types.ActiveClusterInfo{
64+
"us-west": {
65+
ActiveClusterName: "cluster0",
66+
FailoverVersion: 20,
67+
},
68+
},
6569
},
6670
},
6771
},

common/authorization/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Cadence has two authorizer options:
2+
3+
1. OAuthAuthorizer: validates JWTs issued by your Identity Provider and enforces permissions.
4+
2. NoopAuthorizer: turns authorization off.
5+
6+
In order to configure, add an authorization section to Cadence server config [example](https://github.com/cadence-workflow/cadence/blob/master/config/development_oauth.yaml). These fields map 1:1 to the Go structs in [common/config](https://github.com/cadence-workflow/cadence/blob/master/common/config/authorization.go).
7+
8+
### Option A for OAuth : Validate tokens via JWKS
9+
10+
11+
authorization:
12+
oauthAuthorizer:
13+
enable: true
14+
# Reject tokens with excessively long TTL (seconds). Optional but recommended.
15+
maxJwtTTL: 3600
16+
17+
# JWT verification config (algorithm + how to fetch public keys)
18+
jwtCredentials:
19+
algorithm: RS256 # supported: RS256
20+
# publicKey is optional if you supply a JWKS URL (below)
21+
# publicKey: /etc/cadence/keys/idp-public.pem
22+
23+
provider:
24+
jwksURL: "https://YOUR_IDP/.well-known/jwks.json"
25+
# Optional JSONPath-like claims locations used by Cadence:
26+
groupsAttributePath: "groups"
27+
adminAttributePath: "admin"
28+
29+
### Option B for OAuth : Validate tokens via a static public key
30+
31+
32+
authorization:
33+
oauthAuthorizer:
34+
enable: true
35+
maxJwtTTL: 3600
36+
jwtCredentials:
37+
algorithm: RS256
38+
publicKey: /etc/cadence/keys/idp-public.pem
39+
40+
### NoopAuthorizer: Turning authz off
41+
42+
43+
authorization:
44+
noopAuthorizer:
45+
enable: true
46+
47+
## Background
48+
49+
The server constructs an authorization.Attributes object for each API call (actor, API name, domain, optional workflow/tasklist), evaluates the token, and returns an allow/deny Decision. JWTs are expected to contain Cadence-specific claims including groups and (optionally) an admin flag.
50+
51+
### Key structs & functions:
52+
53+
```
54+
authorization.Authorizer interface
55+
56+
authorization.Attributes
57+
58+
authorization.Decision
59+
60+
authorization.JWTClaims
61+
```
62+
63+
When OAuth authZ is enabled, clients must present a valid JWT to the frontend service on every call (Cadence uses the provided token to authorize the API/Domain access). The exact header/wire placement is handled by Cadence’s server middleware and the client transport; the important bit is that the token must validate against your jwksURL/publicKey, include expected claims (groups/admin), and not exceed maxJwtTTL.

common/cache/domainCache.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -890,8 +890,9 @@ func (entry *DomainCacheEntry) NewDomainNotActiveError(currentCluster, activeClu
890890

891891
// IsActive return whether the domain is active in the current cluster,
892892
// - for local domain, it is always active
893-
// - for active-passive domain, it is active if it is not pending active and active cluster is the current cluster
894-
// - for active-active domain, it is active if the current cluster is in the active clusters list
893+
// - for global domain, it is active if it is not pending active and the domain's active cluster is the current cluster or if the domain is active-active and the active cluster of one of the cluster attributes is the current cluster
894+
// TODO(active-active): for active-active domains, we should review this logic because now workflows can be active in different clusters based on the cluster attribute.
895+
// We should also revisit its usage in history service.
895896
func (entry *DomainCacheEntry) IsActiveIn(currentCluster string) bool {
896897
if !entry.IsGlobalDomain() {
897898
// domain is not a global domain, meaning domain is always "active" within each cluster
@@ -902,25 +903,22 @@ func (entry *DomainCacheEntry) IsActiveIn(currentCluster string) bool {
902903
return false
903904
}
904905

905-
// TODO(active-active): review this logic because cluster attribute might be an input
906-
if entry.GetReplicationConfig().IsActiveActive() {
907-
for _, scope := range entry.GetReplicationConfig().ActiveClusters.AttributeScopes {
906+
activeCluster := entry.GetReplicationConfig().ActiveClusterName
907+
if currentCluster == activeCluster {
908+
return true
909+
}
910+
911+
if activeClusters := entry.GetReplicationConfig().ActiveClusters; activeClusters != nil {
912+
for _, scope := range activeClusters.AttributeScopes {
908913
for _, cl := range scope.ClusterAttributes {
909914
if cl.ActiveClusterName == currentCluster {
910915
return true
911916
}
912917
}
913918
}
914-
915-
return false
916-
}
917-
918-
activeCluster := entry.GetReplicationConfig().ActiveClusterName
919-
if currentCluster != activeCluster {
920-
return false
921919
}
922920

923-
return true
921+
return false
924922
}
925923

926924
// IsDomainPendingActive returns whether the domain is in pending active state

common/cache/domainCache_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,23 @@ func Test_IsActiveIn(t *testing.T) {
341341
},
342342
expectIsActive: true,
343343
},
344+
{
345+
msg: "active-active domain on domain level active cluster",
346+
isGlobalDomain: true,
347+
currentCluster: "A",
348+
activeCluster: "A",
349+
activeClusters: &types.ActiveClusters{
350+
AttributeScopes: map[string]types.ClusterAttributeScope{
351+
"region": {
352+
ClusterAttributes: map[string]types.ActiveClusterInfo{
353+
"region0": {ActiveClusterName: "B"},
354+
"region1": {ActiveClusterName: "B"},
355+
},
356+
},
357+
},
358+
},
359+
expectIsActive: true,
360+
},
344361
{
345362
msg: "active-active domain on passive cluster",
346363
isGlobalDomain: true,

common/domain/attrValidator.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,17 @@ func (d *AttrValidatorImpl) validateDomainReplicationConfigForGlobalDomain(
127127
if !isInClusters(activeCluster) {
128128
return errActiveClusterNotInClusters
129129
}
130-
131-
if replicationConfig.IsActiveActive() {
132-
// For active-active domains, also validate that all clusters in ActiveClustersByRegion are valid
133-
for _, cluster := range activeClusters.ActiveClustersByRegion {
134-
if err := d.validateClusterName(cluster.ActiveClusterName); err != nil {
135-
return err
136-
}
137-
138-
if !isInClusters(cluster.ActiveClusterName) {
139-
return errActiveClusterNotInClusters
130+
// For active-active domains, also validate that all clusters in AttributeScopes are valid
131+
if activeClusters != nil && activeClusters.AttributeScopes != nil {
132+
for _, scope := range activeClusters.AttributeScopes {
133+
for _, cluster := range scope.ClusterAttributes {
134+
if err := d.validateClusterName(cluster.ActiveClusterName); err != nil {
135+
return err
136+
}
137+
138+
if !isInClusters(cluster.ActiveClusterName) {
139+
return errActiveClusterNotInClusters
140+
}
140141
}
141142
}
142143
}
@@ -182,3 +183,33 @@ func (d *AttrValidatorImpl) validateClusterName(
182183
}
183184
return nil
184185
}
186+
187+
func (d *AttrValidatorImpl) validateActiveActiveDomainReplicationConfig(
188+
activeClusters *types.ActiveClusters,
189+
) error {
190+
191+
if activeClusters == nil || activeClusters.AttributeScopes == nil {
192+
return nil
193+
}
194+
195+
clusters := d.clusterMetadata.GetEnabledClusterInfo()
196+
197+
for _, scopeData := range activeClusters.AttributeScopes {
198+
for _, activeCluster := range scopeData.ClusterAttributes {
199+
_, ok := clusters[activeCluster.ActiveClusterName]
200+
if !ok {
201+
return &types.BadRequestError{Message: fmt.Sprintf(
202+
"Invalid active cluster name: %v",
203+
activeCluster.ActiveClusterName,
204+
)}
205+
}
206+
if activeCluster.FailoverVersion < 0 {
207+
return &types.BadRequestError{Message: fmt.Sprintf(
208+
"invalid failover version: %d",
209+
activeCluster.FailoverVersion,
210+
)}
211+
}
212+
}
213+
}
214+
return nil
215+
}

0 commit comments

Comments
 (0)