Skip to content

Commit 67282d6

Browse files
author
Travis Tomsu
authored
Adds service account endpoints + ability for fiat-api module to verify service account authorization. (#108)
1 parent f004e6a commit 67282d6

File tree

4 files changed

+83
-23
lines changed

4 files changed

+83
-23
lines changed

fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/FiatPermissionEvaluator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ public boolean hasPermission(Authentication authentication,
7676

7777
String username = getUsername(authentication);
7878
ResourceType r = ResourceType.parse(resourceType);
79-
Authorization a = Authorization.valueOf(authorization.toString());
79+
Authorization a = null;
80+
// Service accounts don't have read/write authorizations.
81+
if (r != ResourceType.SERVICE_ACCOUNT) {
82+
a = Authorization.valueOf(authorization.toString());
83+
}
8084

8185
if (r == ResourceType.APPLICATION) {
8286
val parsedName = Names.parseName(resourceName.toString()).getApp();
@@ -184,6 +188,10 @@ private boolean permissionContains(Authentication authentication,
184188
return containsAuth.apply(permission.getAccounts());
185189
case APPLICATION:
186190
return containsAuth.apply(permission.getApplications());
191+
case SERVICE_ACCOUNT:
192+
return permission.getServiceAccounts()
193+
.stream()
194+
.anyMatch(view -> view.getName().equalsIgnoreCase(resourceName));
187195
default:
188196
return false;
189197
}

fiat-web/src/main/java/com/netflix/spinnaker/fiat/controllers/AuthorizeController.java

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.netflix.spinnaker.fiat.model.resources.Account;
2323
import com.netflix.spinnaker.fiat.model.resources.Application;
2424
import com.netflix.spinnaker.fiat.model.resources.ResourceType;
25+
import com.netflix.spinnaker.fiat.model.resources.ServiceAccount;
2526
import com.netflix.spinnaker.fiat.permissions.PermissionsRepository;
2627
import io.swagger.annotations.ApiOperation;
2728
import org.springframework.beans.factory.annotation.Autowired;
@@ -92,6 +93,53 @@ public Account.View getUserAccount(@PathVariable String userId, @PathVariable St
9293
.orElseThrow(NotFoundException::new);
9394
}
9495

96+
@RequestMapping(value = "/{userId:.+}/applications", method = RequestMethod.GET)
97+
public Set<Application.View> getUserApplications(@PathVariable String userId) {
98+
return permissionsRepository.get(ControllerSupport.convert(userId))
99+
.orElseThrow(NotFoundException::new)
100+
.getApplications()
101+
.stream()
102+
.map(Application::getView)
103+
.collect(Collectors.toSet());
104+
}
105+
106+
@RequestMapping(value = "/{userId:.+}/applications/{applicationName:.+}", method = RequestMethod.GET)
107+
public Application.View getUserApplication(@PathVariable String userId, @PathVariable String applicationName) {
108+
return permissionsRepository.get(ControllerSupport.convert(userId))
109+
.orElseThrow(NotFoundException::new)
110+
.getApplications()
111+
.stream()
112+
.filter(application -> applicationName.equalsIgnoreCase(application.getName()))
113+
.findFirst()
114+
.map(Application::getView)
115+
.orElseThrow(NotFoundException::new);
116+
}
117+
118+
@RequestMapping(value = "/{userId:.+}/serviceAccounts", method = RequestMethod.GET)
119+
public Set<ServiceAccount.View> getServiceAccounts(@PathVariable String userId) {
120+
return permissionsRepository.get(ControllerSupport.convert(userId))
121+
.orElseThrow(NotFoundException::new)
122+
.getServiceAccounts()
123+
.stream()
124+
.map(ServiceAccount::getView)
125+
.collect(Collectors.toSet());
126+
}
127+
128+
@RequestMapping(value = "/{userId:.+}/serviceAccounts/{serviceAccountName:.+}", method = RequestMethod.GET)
129+
public ServiceAccount.View getServiceAccount(@PathVariable String userId,
130+
@PathVariable String serviceAccountName) {
131+
return permissionsRepository.get(ControllerSupport.convert(userId))
132+
.orElseThrow(NotFoundException::new)
133+
.getServiceAccounts()
134+
.stream()
135+
.filter(serviceAccount ->
136+
serviceAccount.getName()
137+
.equalsIgnoreCase(ControllerSupport.convert(serviceAccountName)))
138+
.findFirst()
139+
.orElseThrow(NotFoundException::new)
140+
.getView();
141+
}
142+
95143
@RequestMapping(value = "/{userId:.+}/{resourceType:.+}/{resourceName:.+}/{authorization:.+}", method = RequestMethod.GET)
96144
public void getUserAuthorization(@PathVariable String userId,
97145
@PathVariable String resourceType,
@@ -126,26 +174,4 @@ public void getUserAuthorization(@PathVariable String userId,
126174

127175
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
128176
}
129-
130-
@RequestMapping(value = "/{userId:.+}/applications", method = RequestMethod.GET)
131-
public Set<Application.View> getUserApplications(@PathVariable String userId) {
132-
return permissionsRepository.get(ControllerSupport.convert(userId))
133-
.orElseThrow(NotFoundException::new)
134-
.getApplications()
135-
.stream()
136-
.map(Application::getView)
137-
.collect(Collectors.toSet());
138-
}
139-
140-
@RequestMapping(value = "/{userId:.+}/applications/{applicationName:.+}", method = RequestMethod.GET)
141-
public Application.View getUserApplication(@PathVariable String userId, @PathVariable String applicationName) {
142-
return permissionsRepository.get(ControllerSupport.convert(userId))
143-
.orElseThrow(NotFoundException::new)
144-
.getApplications()
145-
.stream()
146-
.filter(application -> applicationName.equalsIgnoreCase(application.getName()))
147-
.findFirst()
148-
.map(Application::getView)
149-
.orElseThrow(NotFoundException::new);
150-
}
151177
}

fiat-web/src/test/groovy/com/netflix/spinnaker/fiat/controllers/AuthorizeControllerSpec.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,26 @@ class AuthorizeControllerSpec extends Specification {
190190
1 * repository.get("foo") >> Optional.of(foo)
191191
result == bar.view
192192
}
193+
194+
def "should get service accounts from repo"() {
195+
setup:
196+
permissionsRepository.put(unrestrictedUser)
197+
permissionsRepository.put(roleServiceAccountUser)
198+
199+
when:
200+
def expected = objectMapper.writeValueAsString([serviceAccount.view])
201+
202+
then:
203+
mockMvc.perform(get("/authorize/roleServiceAccountUser/serviceAccounts"))
204+
.andExpect(status().isOk())
205+
.andExpect(content().json(expected))
206+
207+
when:
208+
expected = objectMapper.writeValueAsString(serviceAccount.view)
209+
210+
then:
211+
mockMvc.perform(get("/authorize/roleServiceAccountUser/serviceAccounts/svcAcct%40group.com"))
212+
.andExpect(status().isOk())
213+
.andExpect(content().json(expected))
214+
}
193215
}

fiat-web/src/test/groovy/com/netflix/spinnaker/fiat/controllers/FiatSystemTestSupport.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ class FiatSystemTestSupport {
5454
.setRoles([roleA, roleB] as Set)
5555
.setAccounts([restrictedAccount] as Set)
5656
.setApplications([restrictedApp] as Set)
57+
58+
UserPermission roleServiceAccountUser = new UserPermission().setId("roleServiceAccountUser")
59+
.setRoles([roleServiceAccount] as Set)
60+
.setServiceAccounts([serviceAccount] as Set)
5761
}

0 commit comments

Comments
 (0)