16
16
17
17
package co .cask .cdap .security ;
18
18
19
+ import co .cask .cdap .client .AuthorizationClient ;
19
20
import co .cask .cdap .client .NamespaceClient ;
20
21
import co .cask .cdap .client .config .ClientConfig ;
21
22
import co .cask .cdap .client .util .RESTClient ;
22
23
import co .cask .cdap .proto .ConfigEntry ;
23
24
import co .cask .cdap .proto .NamespaceMeta ;
25
+ import co .cask .cdap .proto .element .EntityType ;
26
+ import co .cask .cdap .proto .id .ApplicationId ;
27
+ import co .cask .cdap .proto .id .ArtifactId ;
28
+ import co .cask .cdap .proto .id .DatasetId ;
29
+ import co .cask .cdap .proto .id .DatasetModuleId ;
30
+ import co .cask .cdap .proto .id .DatasetTypeId ;
31
+ import co .cask .cdap .proto .id .EntityId ;
32
+ import co .cask .cdap .proto .id .InstanceId ;
33
+ import co .cask .cdap .proto .id .KerberosPrincipalId ;
24
34
import co .cask .cdap .proto .id .NamespaceId ;
35
+ import co .cask .cdap .proto .id .ProgramId ;
36
+ import co .cask .cdap .proto .id .SecureKeyId ;
37
+ import co .cask .cdap .proto .id .StreamId ;
38
+ import co .cask .cdap .proto .security .Action ;
39
+ import co .cask .cdap .proto .security .Role ;
40
+ import co .cask .cdap .security .authorization .sentry .model .Application ;
41
+ import co .cask .cdap .security .authorization .sentry .model .Artifact ;
42
+ import co .cask .cdap .security .authorization .sentry .model .Authorizable ;
43
+ import co .cask .cdap .security .authorization .sentry .model .Dataset ;
44
+ import co .cask .cdap .security .authorization .sentry .model .DatasetModule ;
45
+ import co .cask .cdap .security .authorization .sentry .model .DatasetType ;
46
+ import co .cask .cdap .security .authorization .sentry .model .Instance ;
47
+ import co .cask .cdap .security .authorization .sentry .model .Namespace ;
48
+ import co .cask .cdap .security .authorization .sentry .model .Program ;
49
+ import co .cask .cdap .security .authorization .sentry .model .SecureKey ;
50
+ import co .cask .cdap .security .authorization .sentry .model .Stream ;
25
51
import co .cask .cdap .test .AudiTestBase ;
52
+ import com .google .common .annotations .VisibleForTesting ;
26
53
import com .google .common .base .Preconditions ;
54
+ import com .google .common .collect .Sets ;
27
55
import com .google .gson .Gson ;
28
56
import com .google .gson .GsonBuilder ;
57
+ import org .apache .hadoop .conf .Configuration ;
58
+ import org .apache .hadoop .security .UserGroupInformation ;
59
+ import org .apache .sentry .provider .db .SentryNoSuchObjectException ;
60
+ import org .apache .sentry .provider .db .generic .service .thrift .SentryGenericServiceClient ;
61
+ import org .apache .sentry .provider .db .generic .service .thrift .SentryGenericServiceClientFactory ;
62
+ import org .apache .sentry .provider .db .generic .service .thrift .TAuthorizable ;
63
+ import org .apache .sentry .provider .db .generic .service .thrift .TSentryGrantOption ;
64
+ import org .apache .sentry .provider .db .generic .service .thrift .TSentryPrivilege ;
65
+ import org .apache .sentry .service .thrift .ServiceConstants ;
29
66
import org .junit .Before ;
30
67
68
+ import java .io .IOException ;
69
+ import java .net .URI ;
70
+ import java .net .URISyntaxException ;
71
+ import java .util .ArrayList ;
72
+ import java .util .HashSet ;
73
+ import java .util .LinkedList ;
74
+ import java .util .List ;
75
+ import java .util .Map ;
76
+ import java .util .Set ;
31
77
import javax .annotation .Nullable ;
78
+ import javax .security .auth .callback .Callback ;
79
+ import javax .security .auth .callback .CallbackHandler ;
80
+ import javax .security .auth .callback .NameCallback ;
81
+ import javax .security .auth .callback .PasswordCallback ;
82
+ import javax .security .auth .callback .UnsupportedCallbackException ;
83
+ import javax .security .auth .login .LoginContext ;
84
+ import javax .security .auth .login .LoginException ;
32
85
33
86
/**
34
87
* Authorization test base for all authorization tests
@@ -45,10 +98,33 @@ public abstract class AuthorizationTestBase extends AudiTestBase {
45
98
protected static final String VERSION = "1.0.0" ;
46
99
protected static final String NO_PRIVILEGE_MSG = "does not have privileges to access entity" ;
47
100
101
+ private static final String COMPONENT = "cdap" ;
102
+ private static final String INSTANCE_NAME = "cdap" ;
103
+ private static final Role DUMMY_ROLE = new Role ("dummy" );
104
+
105
+ // TODO: Remove this when we migrate to wildcard privilege
106
+ protected Set <EntityId > cleanUpEntities ;
107
+ private SentryGenericServiceClient sentryClient ;
108
+ private AuthorizationClient authorizationClient ;
109
+
48
110
// General test namespace
49
111
protected NamespaceMeta testNamespace = getNamespaceMeta (new NamespaceId ("authorization" ), null , null ,
50
112
null , null , null , null );
51
113
114
+ @ Override
115
+ public void setUp () throws Exception {
116
+ sentryClient = SentryGenericServiceClientFactory .create (getSentryConfig ());
117
+ // TODO: remove this once caching in sentry is fixed
118
+ ClientConfig adminConfig = getClientConfig (fetchAccessToken (ADMIN_USER , ADMIN_USER ));
119
+ RESTClient adminClient = new RESTClient (adminConfig );
120
+ authorizationClient = new AuthorizationClient (adminConfig , adminClient );
121
+ userGrant (ADMIN_USER , NamespaceId .DEFAULT , Action .ADMIN );
122
+ invalidateCache ();
123
+ super .setUp ();
124
+ userRevoke (ADMIN_USER );
125
+ cleanUpEntities = new HashSet <>();
126
+ }
127
+
52
128
@ Before
53
129
public void setup () throws Exception {
54
130
ConfigEntry configEntry = this .getMetaClient ().getCDAPConfig ().get ("security.authorization.enabled" );
@@ -57,16 +133,78 @@ public void setup() throws Exception {
57
133
Preconditions .checkState (Boolean .parseBoolean (configEntry .getValue ()), "Authorization not enabled." );
58
134
}
59
135
136
+ @ Override
137
+ public void tearDown () throws Exception {
138
+ // we have to grant ADMIN privileges to all clean up entites such that these entities can be deleted
139
+ for (EntityId entityId : cleanUpEntities ) {
140
+ userGrant (ADMIN_USER , entityId , Action .ADMIN );
141
+ }
142
+ userGrant (ADMIN_USER , testNamespace .getNamespaceId (), Action .ADMIN );
143
+ userGrant (ADMIN_USER , NamespaceId .DEFAULT , Action .ADMIN );
144
+ invalidateCache ();
145
+ // teardown in parent deletes all entities
146
+ super .tearDown ();
147
+ // reset the test by revoking privileges from all users.
148
+ userRevoke (ADMIN_USER );
149
+ userRevoke (ALICE );
150
+ userRevoke (BOB );
151
+ userRevoke (CAROL );
152
+ userRevoke (EVE );
153
+ sentryClient .close ();
154
+ }
155
+
60
156
protected NamespaceId createAndRegisterNamespace (NamespaceMeta namespaceMeta , ClientConfig config ,
61
- RESTClient client ) throws Exception {
157
+ RESTClient client ) throws Exception {
62
158
try {
63
159
new NamespaceClient (config , client ).create (namespaceMeta );
160
+ cleanUpEntities .add (namespaceMeta .getNamespaceId ());
64
161
} finally {
65
162
registerForDeletion (namespaceMeta .getNamespaceId ());
66
163
}
67
164
return namespaceMeta .getNamespaceId ();
68
165
}
69
166
167
+ /**
168
+ * Grants action privilege to user on entityId. Creates a role for the user. Grant action privilege
169
+ * on that role, and add the role to the group the user belongs to. All done through sentry.
170
+ * @param user The user we want to grant privilege to.
171
+ * @param entityId The entity we want to grant privilege on.
172
+ * @param action The privilege we want to grant.
173
+ */
174
+ protected void userGrant (String user , EntityId entityId , Action action ) throws Exception {
175
+ // create role and add to group
176
+ // TODO: use a different user as Sentry Admin (neither CDAP, nor an user used in our tests)
177
+ sentryClient .createRoleIfNotExist (ADMIN_USER , user , COMPONENT );
178
+ sentryClient .addRoleToGroups (ADMIN_USER , user , COMPONENT , Sets .newHashSet (user ));
179
+
180
+ // create authorizable list
181
+ List <TAuthorizable > authorizables = toTAuthorizable (entityId );
182
+ TSentryPrivilege privilege = new TSentryPrivilege (COMPONENT , INSTANCE_NAME , authorizables , action .name ());
183
+ privilege .setGrantOption (TSentryGrantOption .TRUE );
184
+ sentryClient .grantPrivilege (ADMIN_USER , user , COMPONENT , privilege );
185
+ }
186
+
187
+ protected void invalidateCache () throws Exception {
188
+ // TODO: Hack to invalidate cache in sentry authorizer. Remove once cache problem is solved.
189
+ authorizationClient .dropRole (DUMMY_ROLE );
190
+ }
191
+
192
+ /**
193
+ * Revokes all privileges from user. Deletes the user role through sentry.
194
+ *
195
+ * @param user The user we want to revoke privilege from.
196
+ */
197
+ protected void userRevoke (String user ) throws Exception {
198
+ try {
199
+ sentryClient .deleteRoleToGroups (ADMIN_USER , user , COMPONENT , Sets .newHashSet (user ));
200
+ } catch (SentryNoSuchObjectException e ) {
201
+ // skip a role that hasn't been added to the user
202
+ } finally {
203
+ sentryClient .dropRoleIfExists (ADMIN_USER , user , COMPONENT );
204
+ invalidateCache ();
205
+ }
206
+ }
207
+
70
208
protected NamespaceMeta getNamespaceMeta (NamespaceId namespaceId , @ Nullable String principal ,
71
209
@ Nullable String groupName , @ Nullable String keytabURI ,
72
210
@ Nullable String rootDirectory , @ Nullable String hbaseNamespace ,
@@ -82,4 +220,129 @@ protected NamespaceMeta getNamespaceMeta(NamespaceId namespaceId, @Nullable Stri
82
220
.setHiveDatabase (hiveDatabase )
83
221
.build ();
84
222
}
223
+
224
+ private Configuration getSentryConfig () throws IOException , LoginException , URISyntaxException {
225
+
226
+ String hostUri = super .getInstanceURI ();
227
+ String sentryRpcAddr = new URI (hostUri ).getHost ();
228
+ String sentryPrincipal = "sentry/" + sentryRpcAddr + "@CONTINUUITY.NET" ;
229
+ String sentryRpcPort = "8038" ;
230
+
231
+ Configuration conf = new Configuration (false );
232
+ conf .clear ();
233
+ conf .set (ServiceConstants .ServerConfig .SECURITY_MODE , ServiceConstants .ServerConfig .SECURITY_MODE_KERBEROS );
234
+ conf .set (ServiceConstants .ServerConfig .PRINCIPAL , sentryPrincipal );
235
+ conf .set (ServiceConstants .ClientConfig .SERVER_RPC_ADDRESS , sentryRpcAddr );
236
+ conf .set (ServiceConstants .ClientConfig .SERVER_RPC_PORT , sentryRpcPort );
237
+
238
+ // Log in to Kerberos
239
+ UserGroupInformation .setConfiguration (conf );
240
+ LoginContext lc = kinit ();
241
+ UserGroupInformation .loginUserFromSubject (lc .getSubject ());
242
+ return conf ;
243
+ }
244
+
245
+ private static LoginContext kinit () throws LoginException {
246
+ LoginContext lc = new LoginContext (BasicAuthorizationTest .class .getSimpleName (), new CallbackHandler () {
247
+ public void handle (Callback [] callbacks ) throws IOException , UnsupportedCallbackException {
248
+ for (Callback c : callbacks ) {
249
+ if (c instanceof NameCallback ) {
250
+ ((NameCallback ) c ).setName (ADMIN_USER );
251
+ }
252
+ if (c instanceof PasswordCallback ) {
253
+ ((PasswordCallback ) c ).setPassword (ADMIN_USER .toCharArray ());
254
+ }
255
+ }
256
+ }
257
+ });
258
+ lc .login ();
259
+ return lc ;
260
+ }
261
+
262
+ private List <TAuthorizable > toTAuthorizable (EntityId entityId ) {
263
+ List <org .apache .sentry .core .common .Authorizable > authorizables = toSentryAuthorizables (entityId );
264
+ List <TAuthorizable > tAuthorizables = new ArrayList <>();
265
+ for (org .apache .sentry .core .common .Authorizable authorizable : authorizables ) {
266
+ tAuthorizables .add (new TAuthorizable (authorizable .getTypeName (), authorizable .getName ()));
267
+ }
268
+ return tAuthorizables ;
269
+ }
270
+
271
+ @ VisibleForTesting
272
+ List <org .apache .sentry .core .common .Authorizable > toSentryAuthorizables (final EntityId entityId ) {
273
+ List <org .apache .sentry .core .common .Authorizable > authorizables = new LinkedList <>();
274
+ toAuthorizables (entityId , authorizables );
275
+ return authorizables ;
276
+ }
277
+
278
+ private void toAuthorizables (EntityId entityId , List <? super Authorizable > authorizables ) {
279
+ EntityType entityType = entityId .getEntityType ();
280
+ switch (entityType ) {
281
+ case INSTANCE :
282
+ authorizables .add (new Instance (((InstanceId ) entityId ).getInstance ()));
283
+ break ;
284
+ case NAMESPACE :
285
+ toAuthorizables (new InstanceId (INSTANCE_NAME ), authorizables );
286
+ authorizables .add (new Namespace (((NamespaceId ) entityId ).getNamespace ()));
287
+ break ;
288
+ case ARTIFACT :
289
+ ArtifactId artifactId = (ArtifactId ) entityId ;
290
+ toAuthorizables (artifactId .getParent (), authorizables );
291
+ authorizables .add (new Artifact (artifactId .getArtifact ()));
292
+ break ;
293
+ case APPLICATION :
294
+ ApplicationId applicationId = (ApplicationId ) entityId ;
295
+ toAuthorizables (applicationId .getParent (), authorizables );
296
+ authorizables .add (new Application (applicationId .getApplication ()));
297
+ break ;
298
+ case DATASET :
299
+ DatasetId dataset = (DatasetId ) entityId ;
300
+ toAuthorizables (dataset .getParent (), authorizables );
301
+ authorizables .add (new Dataset (dataset .getDataset ()));
302
+ break ;
303
+ case DATASET_MODULE :
304
+ DatasetModuleId datasetModuleId = (DatasetModuleId ) entityId ;
305
+ toAuthorizables (datasetModuleId .getParent (), authorizables );
306
+ authorizables .add (new DatasetModule (datasetModuleId .getModule ()));
307
+ break ;
308
+ case DATASET_TYPE :
309
+ DatasetTypeId datasetTypeId = (DatasetTypeId ) entityId ;
310
+ toAuthorizables (datasetTypeId .getParent (), authorizables );
311
+ authorizables .add (new DatasetType (datasetTypeId .getType ()));
312
+ break ;
313
+ case STREAM :
314
+ StreamId streamId = (StreamId ) entityId ;
315
+ toAuthorizables (streamId .getParent (), authorizables );
316
+ authorizables .add (new Stream ((streamId ).getStream ()));
317
+ break ;
318
+ case PROGRAM :
319
+ ProgramId programId = (ProgramId ) entityId ;
320
+ toAuthorizables (programId .getParent (), authorizables );
321
+ authorizables .add (new Program (programId .getType (), programId .getProgram ()));
322
+ break ;
323
+ case SECUREKEY :
324
+ SecureKeyId secureKeyId = (SecureKeyId ) entityId ;
325
+ toAuthorizables (secureKeyId .getParent (), authorizables );
326
+ authorizables .add (new SecureKey (secureKeyId .getName ()));
327
+ break ;
328
+ case KERBEROSPRINCIPAL :
329
+ KerberosPrincipalId principalId = (KerberosPrincipalId ) entityId ;
330
+ toAuthorizables (new InstanceId (INSTANCE_NAME ), authorizables );
331
+ authorizables .add (new co .cask .cdap .security .authorization .sentry .model .Principal (principalId .getPrincipal ()));
332
+ break ;
333
+ default :
334
+ throw new IllegalArgumentException (String .format ("The entity %s is of unknown type %s" , entityId , entityType ));
335
+ }
336
+ }
337
+
338
+ // TODO: Remove this when we migrate to wildcard privilege
339
+ protected void setUpPrivilegeAndRegisterForDeletion (String user ,
340
+ Map <EntityId , Set <Action >> neededPrivileges ) throws Exception {
341
+ for (Map .Entry <EntityId , Set <Action >> privilege : neededPrivileges .entrySet ()) {
342
+ for (Action action : privilege .getValue ()) {
343
+ userGrant (user , privilege .getKey (), action );
344
+ cleanUpEntities .add (privilege .getKey ());
345
+ }
346
+ }
347
+ }
85
348
}
0 commit comments