Skip to content

Commit 6845c1d

Browse files
committed
Optimize health check using self subject rules review
1 parent 79f9238 commit 6845c1d

File tree

5 files changed

+186
-7
lines changed

5 files changed

+186
-7
lines changed

operator/src/main/java/oracle/kubernetes/operator/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ private static void begin() {
220220
HealthCheckHelper healthCheck = new HealthCheckHelper(namespace, targetNamespaces);
221221
version = healthCheck.performK8sVersionCheck();
222222
healthCheck.performNonSecurityChecks();
223-
healthCheck.performSecurityChecks();
223+
healthCheck.performSecurityChecks(version);
224224
} catch (ApiException e) {
225225
LOGGER.warning(MessageKeys.EXCEPTION, e);
226226
}

operator/src/main/java/oracle/kubernetes/operator/helpers/AuthorizationProxy.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import io.kubernetes.client.models.V1ResourceAttributes;
1111
import io.kubernetes.client.models.V1SelfSubjectAccessReview;
1212
import io.kubernetes.client.models.V1SelfSubjectAccessReviewSpec;
13+
import io.kubernetes.client.models.V1SelfSubjectRulesReview;
14+
import io.kubernetes.client.models.V1SelfSubjectRulesReviewSpec;
1315
import io.kubernetes.client.models.V1SubjectAccessReview;
1416
import io.kubernetes.client.models.V1SubjectAccessReviewSpec;
1517
import io.kubernetes.client.models.V1SubjectAccessReviewStatus;
@@ -232,4 +234,18 @@ private V1ResourceAttributes prepareResourceAttributes(Operation operation, Reso
232234
LOGGER.exiting(resourceAttributes);
233235
return resourceAttributes;
234236
}
237+
238+
public V1SelfSubjectRulesReview review(String namespace) {
239+
V1SelfSubjectRulesReview subjectRulesReview = new V1SelfSubjectRulesReview();
240+
V1SelfSubjectRulesReviewSpec spec = new V1SelfSubjectRulesReviewSpec();
241+
spec.setNamespace(namespace);
242+
subjectRulesReview.setSpec(spec);
243+
CallBuilderFactory factory = ContainerResolver.getInstance().getContainer().getSPI(CallBuilderFactory.class);
244+
try {
245+
return factory.create().createSelfSubjectRulesReview(subjectRulesReview);
246+
} catch (ApiException e) {
247+
LOGGER.warning(MessageKeys.EXCEPTION, e);
248+
return null;
249+
}
250+
}
235251
}

operator/src/main/java/oracle/kubernetes/operator/helpers/CallBuilder.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import io.kubernetes.client.models.V1PodList;
3838
import io.kubernetes.client.models.V1Secret;
3939
import io.kubernetes.client.models.V1SelfSubjectAccessReview;
40+
import io.kubernetes.client.models.V1SelfSubjectRulesReview;
4041
import io.kubernetes.client.models.V1Service;
4142
import io.kubernetes.client.models.V1ServiceList;
4243
import io.kubernetes.client.models.V1Status;
@@ -1420,6 +1421,41 @@ public Step createSelfSubjectAccessReviewAsync(V1SelfSubjectAccessReview body, R
14201421
return createRequestAsync(responseStep, new RequestParams("createSelfSubjectAccessReview", null, null, body), CREATE_SELFSUBJECTACCESSREVIEW);
14211422
}
14221423

1424+
/* Self Subject Rules Review */
1425+
1426+
/**
1427+
* Create self subject rules review
1428+
* @param body Body
1429+
* @return Created self subject rules review
1430+
* @throws ApiException API Exception
1431+
*/
1432+
public V1SelfSubjectRulesReview createSelfSubjectRulesReview(V1SelfSubjectRulesReview body) throws ApiException {
1433+
ApiClient client = helper.take();
1434+
try {
1435+
return new AuthorizationV1Api(client).createSelfSubjectRulesReview(body, pretty);
1436+
} finally {
1437+
helper.recycle(client);
1438+
}
1439+
}
1440+
1441+
private com.squareup.okhttp.Call createSelfSubjectRulesReviewAsync(ApiClient client, V1SelfSubjectRulesReview body, ApiCallback<V1SelfSubjectRulesReview> callback) throws ApiException {
1442+
return new AuthorizationV1Api(client).createSelfSubjectRulesReviewAsync(body, pretty, callback);
1443+
}
1444+
1445+
private final CallFactory<V1SelfSubjectRulesReview> CREATE_SELFSUBJECTRULESREVIEW = (requestParams, usage, cont, callback) -> {
1446+
return createSelfSubjectRulesReviewAsync(usage, (V1SelfSubjectRulesReview) requestParams.body, callback);
1447+
};
1448+
1449+
/**
1450+
* Asynchronous step for creating self subject rules review
1451+
* @param body Body
1452+
* @param responseStep Response step for when call completes
1453+
* @return Asynchronous step
1454+
*/
1455+
public Step createSelfSubjectRulesReviewAsync(V1SelfSubjectRulesReview body, ResponseStep<V1SelfSubjectRulesReview> responseStep) {
1456+
return createRequestAsync(responseStep, new RequestParams("createSelfSubjectRulesReview", null, null, body), CREATE_SELFSUBJECTRULESREVIEW);
1457+
}
1458+
14231459
/* Token Review */
14241460

14251461
/**

operator/src/main/java/oracle/kubernetes/operator/helpers/HealthCheckHelper.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import io.kubernetes.client.ApiException;
77
import io.kubernetes.client.models.V1PersistentVolume;
88
import io.kubernetes.client.models.V1PersistentVolumeList;
9+
import io.kubernetes.client.models.V1ResourceRule;
10+
import io.kubernetes.client.models.V1SelfSubjectRulesReview;
11+
import io.kubernetes.client.models.V1SubjectRulesReviewStatus;
912
import io.kubernetes.client.models.VersionInfo;
1013
import oracle.kubernetes.weblogic.domain.v1.Domain;
1114
import oracle.kubernetes.weblogic.domain.v1.DomainList;
@@ -130,10 +133,10 @@ public void performNonSecurityChecks() throws ApiException {
130133

131134
/**
132135
* Verify Access.
133-
*
136+
* @param version Kubernetes version
134137
* @throws ApiException exception for k8s API
135138
**/
136-
public void performSecurityChecks() throws ApiException {
139+
public void performSecurityChecks(KubernetesVersion version) throws ApiException {
137140

138141
// Validate namespace
139142
if (DEFAULT_NAMESPACE.equals(operatorNamespace)) {
@@ -143,7 +146,51 @@ public void performSecurityChecks() throws ApiException {
143146
// Validate RBAC or ABAC policies allow service account to perform required operations
144147
AuthorizationProxy ap = new AuthorizationProxy();
145148
LOGGER.info(MessageKeys.VERIFY_ACCESS_START);
146-
149+
150+
if (version.major > 1 || version.minor >= 8) {
151+
boolean rulesReviewSuccessful = true;
152+
for (String ns : targetNamespaces) {
153+
V1SelfSubjectRulesReview review = ap.review(ns);
154+
if (review == null) {
155+
rulesReviewSuccessful = false;
156+
break;
157+
}
158+
159+
V1SubjectRulesReviewStatus status = review.getStatus();
160+
List<V1ResourceRule> rules = status.getResourceRules();
161+
162+
for (AuthorizationProxy.Resource r : namespaceAccessChecks.keySet()) {
163+
for (AuthorizationProxy.Operation op : namespaceAccessChecks.get(r)) {
164+
check(rules, r, op);
165+
}
166+
}
167+
for (AuthorizationProxy.Resource r : clusterAccessChecks.keySet()) {
168+
for (AuthorizationProxy.Operation op : clusterAccessChecks.get(r)) {
169+
check(rules, r, op);
170+
}
171+
}
172+
}
173+
if (!targetNamespaces.contains("default")) {
174+
V1SelfSubjectRulesReview review = ap.review("default");
175+
if (review == null) {
176+
rulesReviewSuccessful = false;
177+
} else {
178+
V1SubjectRulesReviewStatus status = review.getStatus();
179+
List<V1ResourceRule> rules = status.getResourceRules();
180+
181+
for (AuthorizationProxy.Resource r : clusterAccessChecks.keySet()) {
182+
for (AuthorizationProxy.Operation op : clusterAccessChecks.get(r)) {
183+
check(rules, r, op);
184+
}
185+
}
186+
}
187+
}
188+
189+
if (rulesReviewSuccessful) {
190+
return;
191+
}
192+
}
193+
147194
for (AuthorizationProxy.Resource r : namespaceAccessChecks.keySet()) {
148195
for (AuthorizationProxy.Operation op : namespaceAccessChecks.get(r)) {
149196

@@ -164,7 +211,38 @@ public void performSecurityChecks() throws ApiException {
164211
}
165212
}
166213
}
214+
215+
private void check(List<V1ResourceRule> rules, AuthorizationProxy.Resource r, AuthorizationProxy.Operation op) {
216+
String verb = op.name();
217+
String apiGroup = r.getAPIGroup();
218+
String resource = r.getResource();
219+
String sub = r.getSubResource();
220+
if (sub != null && !sub.isEmpty()) {
221+
resource = resource + "/" + sub;
222+
}
223+
for (V1ResourceRule rule : rules) {
224+
List<String> ruleApiGroups = rule.getApiGroups();
225+
if (apiGroupMatch(ruleApiGroups, apiGroup)) {
226+
List<String> ruleResources = rule.getResources();
227+
if (ruleResources != null && ruleResources.contains(resource)) {
228+
List<String> ruleVerbs = rule.getVerbs();
229+
if (ruleVerbs != null && ruleVerbs.contains(verb)) {
230+
return;
231+
}
232+
}
233+
}
234+
}
235+
236+
LOGGER.warning(MessageKeys.VERIFY_ACCESS_DENIED, op, r.getResource());
237+
}
167238

239+
private boolean apiGroupMatch(List<String> ruleApiGroups, String apiGroup) {
240+
if (apiGroup == null || apiGroup.isEmpty()) {
241+
return ruleApiGroups == null || ruleApiGroups.isEmpty() || ruleApiGroups.contains("");
242+
}
243+
return ruleApiGroups != null && ruleApiGroups.contains(apiGroup);
244+
}
245+
168246
/**
169247
* Major and minor version of Kubernetes API Server
170248
*

operator/src/test/java/oracle/kubernetes/operator/HealthCheckHelperTest.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
import io.kubernetes.client.models.V1ServiceAccount;
1313
import io.kubernetes.client.models.V1beta1ClusterRole;
1414
import io.kubernetes.client.models.V1beta1ClusterRoleBinding;
15+
import io.kubernetes.client.models.V1beta1PolicyRule;
1516
import io.kubernetes.client.models.V1beta1RoleBinding;
17+
import io.kubernetes.client.models.V1beta1RoleRef;
18+
import io.kubernetes.client.models.V1beta1Subject;
1619
import io.kubernetes.client.util.ClientBuilder;
1720
import io.kubernetes.client.util.Config;
1821
import oracle.kubernetes.TestUtils;
@@ -25,6 +28,7 @@
2528
import oracle.kubernetes.operator.helpers.ClientFactory;
2629
import oracle.kubernetes.operator.helpers.ClientPool;
2730
import oracle.kubernetes.operator.helpers.HealthCheckHelper;
31+
import oracle.kubernetes.operator.helpers.HealthCheckHelper.KubernetesVersion;
2832
import oracle.kubernetes.operator.logging.LoggingFacade;
2933
import oracle.kubernetes.operator.logging.LoggingFactory;
3034
import oracle.kubernetes.operator.logging.LoggingFormatter;
@@ -51,6 +55,7 @@
5155

5256
public class HealthCheckHelperTest {
5357

58+
private KubernetesVersion version;
5459
private HealthCheckHelper unitHealthCheckHelper;
5560

5661
private final static String UNIT_NAMESPACE = "unit-test-namespace";
@@ -76,7 +81,7 @@ public void setUp() throws Exception {
7681
createNamespace(UNIT_NAMESPACE);
7782

7883
unitHealthCheckHelper = new HealthCheckHelper(UNIT_NAMESPACE, Collections.singleton(UNIT_NAMESPACE));
79-
84+
8085
userProjects = UserProjects.createUserProjectsDirectory();
8186
}
8287

@@ -115,6 +120,8 @@ public void testAccountNoPrivs() throws Exception {
115120
String token = new String(secret.getData().get("token"), StandardCharsets.UTF_8);
116121
*/
117122

123+
applyMinimumSecurity(apiClient, UNIT_NAMESPACE, "alice");
124+
118125
ContainerResolver.getInstance().getContainer().getComponents().put(
119126
ProcessingConstants.MAIN_COMPONENT_NAME,
120127
Component.createFor(
@@ -136,7 +143,8 @@ public ApiClient get() {
136143

137144
ClientPool.getInstance().drain();
138145

139-
unitHealthCheckHelper.performSecurityChecks();
146+
version = unitHealthCheckHelper.performK8sVersionCheck();
147+
unitHealthCheckHelper.performSecurityChecks(version);
140148
hdlr.flush();
141149
String logOutput = bos.toString();
142150

@@ -186,7 +194,8 @@ public ApiClient get() {
186194

187195
ClientPool.getInstance().drain();
188196

189-
unitHealthCheckHelper.performSecurityChecks();
197+
version = unitHealthCheckHelper.performK8sVersionCheck();
198+
unitHealthCheckHelper.performSecurityChecks(version);
190199
hdlr.flush();
191200
String logOutput = bos.toString();
192201

@@ -195,6 +204,46 @@ public ApiClient get() {
195204
bos.reset();
196205
}
197206

207+
private void applyMinimumSecurity(ApiClient apiClient, String namespace, String sa) throws Exception {
208+
V1beta1ClusterRole clusterRole = new V1beta1ClusterRole();
209+
clusterRole.setMetadata(new V1ObjectMeta()
210+
.name("test-role")
211+
.putLabelsItem("weblogic.operatorName", namespace));
212+
clusterRole.addRulesItem(new V1beta1PolicyRule().addNonResourceURLsItem("/version/*").addVerbsItem("get"));
213+
clusterRole.addRulesItem(new V1beta1PolicyRule().addResourcesItem("selfsubjectrulesreviews")
214+
.addApiGroupsItem("authorization.k8s.io").addVerbsItem("create"));
215+
V1beta1ClusterRoleBinding clusterRoleBinding = new V1beta1ClusterRoleBinding();
216+
clusterRoleBinding.setMetadata(new V1ObjectMeta()
217+
.name(namespace + "-test-role")
218+
.putLabelsItem("weblogic.operatorName", namespace));
219+
clusterRoleBinding.addSubjectsItem(new V1beta1Subject()
220+
.kind("ServiceAccount")
221+
.apiGroup("")
222+
.name(sa).namespace(namespace));
223+
clusterRoleBinding.roleRef(new V1beta1RoleRef()
224+
.kind("ClusterRole")
225+
.apiGroup("rbac.authorization.k8s.io")
226+
.name("test-role"));
227+
RbacAuthorizationV1beta1Api rbac = new RbacAuthorizationV1beta1Api(apiClient);
228+
229+
try {
230+
rbac.createClusterRole(clusterRole, "false");
231+
} catch (ApiException api) {
232+
if (api.getCode() != CallBuilder.CONFLICT) {
233+
throw api;
234+
}
235+
rbac.replaceClusterRole(clusterRole.getMetadata().getName(), clusterRole, "false");
236+
}
237+
try {
238+
rbac.createClusterRoleBinding(clusterRoleBinding, "false");
239+
} catch (ApiException api) {
240+
if (api.getCode() != CallBuilder.CONFLICT) {
241+
throw api;
242+
}
243+
rbac.replaceClusterRoleBinding(clusterRoleBinding.getMetadata().getName(), clusterRoleBinding, "false");
244+
}
245+
}
246+
198247
private void applySecurity(ApiClient apiClient, String namespace, String sa) throws Exception {
199248
CreateOperatorInputs inputs = readDefaultInputsFile()
200249
.namespace(namespace)

0 commit comments

Comments
 (0)