Skip to content

Commit

Permalink
feat(gcb): Add permissioning to Google Cloud Build accounts (#523)
Browse files Browse the repository at this point in the history
* feat(gcb): Add permissioning to Google Cloud Build

Co-authored-by: Sirisha Vadrevu <sirishavadrevu@gmail.com>

* chore(igor): Clean up code
Added missing whitespace
  • Loading branch information
afcastano authored and mergify[bot] committed Oct 23, 2019
1 parent 9430260 commit 486b26a
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.netflix.spinnaker.igor.build

import com.netflix.spinnaker.igor.config.ConcourseProperties
import com.netflix.spinnaker.igor.config.GitlabCiProperties
import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties
import com.netflix.spinnaker.igor.config.JenkinsProperties
import com.netflix.spinnaker.igor.config.TravisProperties
import com.netflix.spinnaker.igor.config.WerckerProperties
Expand Down Expand Up @@ -69,6 +70,9 @@ class InfoController {
@Autowired(required = false)
ConcourseProperties concourseProperties

@Autowired(required = false)
GoogleCloudBuildProperties gcbProperties

@RequestMapping(value = '/masters', method = RequestMethod.GET)
@PostFilter("hasPermission(filterObject, 'BUILD_SERVICE', 'READ')")
List<String> listMasters(@RequestParam(value = "type", defaultValue = "") String type) {
Expand All @@ -84,7 +88,13 @@ class InfoController {

@RequestMapping(value = '/buildServices', method = RequestMethod.GET)
List<BuildService> getAllBuildServices() {
buildServices.allBuildServices
List<BuildService> allBuildServices = new ArrayList<>(buildServices.allBuildServices)
// GCB accounts are not part of com.netflix.spinnaker.igor.service.BuildServices class.
if (gcbProperties != null) {
allBuildServices.addAll(gcbProperties.getGcbBuildServices())
}

return allBuildServices
}

@RequestMapping(value = '/jobs/{master:.+}', method = RequestMethod.GET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ enum BuildServiceProvider {
TRAVIS,
CONCOURSE,
GITLAB_CI,
WERCKER
WERCKER,
GCB
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

package com.netflix.spinnaker.igor.config;

import com.netflix.spinnaker.fiat.model.resources.Permissions;
import com.netflix.spinnaker.igor.model.BuildServiceProvider;
import com.netflix.spinnaker.igor.service.BuildService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

Expand All @@ -25,11 +29,26 @@
public class GoogleCloudBuildProperties {
private List<Account> accounts;

public List<BuildService> getGcbBuildServices() {
return this.accounts.stream().map(BuildService::getView).collect(Collectors.toList());
}

@Data
public static class Account {
public static class Account implements BuildService {
private String name;
private String project;
private String subscriptionName;
private String jsonKey;
private Permissions.Builder permissions = new Permissions.Builder();

@Override
public BuildServiceProvider getBuildServiceProvider() {
return BuildServiceProvider.GCB;
}

@Override
public Permissions getPermissions() {
return this.permissions.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import retrofit.http.Query;

Expand All @@ -34,6 +36,7 @@ public class GoogleCloudBuildController {
private final GoogleCloudBuildParser googleCloudBuildParser;

@RequestMapping(value = "/accounts", method = RequestMethod.GET)
@PostFilter("hasPermission(filterObject, 'BUILD_SERVICE', 'READ')")
List<String> getAccounts() {
return googleCloudBuildAccountRepository.getAccounts();
}
Expand All @@ -42,6 +45,7 @@ List<String> getAccounts() {
value = "/builds/create/{account}",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'WRITE')")
Build createBuild(@PathVariable String account, @RequestBody String buildString) {
Build build = googleCloudBuildParser.parse(buildString, Build.class);
return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).createBuild(build);
Expand All @@ -62,11 +66,13 @@ void updateBuild(
}

@RequestMapping(value = "/builds/{account}/{buildId}", method = RequestMethod.GET)
@PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'READ')")
Build getBuild(@PathVariable String account, @PathVariable String buildId) {
return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).getBuild(buildId);
}

@RequestMapping(value = "/builds/{account}/{buildId}/artifacts", method = RequestMethod.GET)
@PreAuthorize("hasPermission(#account, 'BUILD_SERVICE', 'READ')")
List<Artifact> getArtifacts(@PathVariable String account, @PathVariable String buildId) {
return googleCloudBuildAccountRepository.getGoogleCloudBuild(account).getArtifacts(buildId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.netflix.spinnaker.igor.build
import com.netflix.spinnaker.fiat.model.Authorization
import com.netflix.spinnaker.fiat.model.resources.Permissions
import com.netflix.spinnaker.igor.config.GitlabCiProperties
import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties
import com.netflix.spinnaker.igor.config.JenkinsConfig
import com.netflix.spinnaker.igor.config.JenkinsProperties
import com.netflix.spinnaker.igor.config.TravisProperties
Expand Down Expand Up @@ -55,6 +56,7 @@ class InfoControllerSpec extends Specification {
JenkinsProperties jenkinsProperties
TravisProperties travisProperties
GitlabCiProperties gitlabCiProperties
GoogleCloudBuildProperties gcbProperties

@Shared
JenkinsService service
Expand All @@ -70,10 +72,14 @@ class InfoControllerSpec extends Specification {
server = new MockWebServer()
}

void createMocks(Map<String, BuildOperations> buildServices) {
void createMocks(Map<String, BuildOperations> buildServices, List<GoogleCloudBuildProperties.Account> gcbAccounts = null) {
cache = Mock(BuildCache)
this.buildServices = new BuildServices()
this.buildServices.addServices(buildServices)
if (gcbAccounts != null) {
this.gcbProperties = new GoogleCloudBuildProperties()
this.gcbProperties.accounts = gcbAccounts
}
jenkinsProperties = Mock(JenkinsProperties)
travisProperties = Mock(TravisProperties)
gitlabCiProperties = Mock(GitlabCiProperties)
Expand All @@ -82,10 +88,18 @@ class InfoControllerSpec extends Specification {
buildServices: this.buildServices,
jenkinsProperties: jenkinsProperties,
travisProperties: travisProperties,
gitlabCiProperties: gitlabCiProperties))
gitlabCiProperties: gitlabCiProperties,
gcbProperties: gcbProperties))
.build()
}

GoogleCloudBuildProperties.Account createGCBAccount(String name) {
GoogleCloudBuildProperties.Account account = new GoogleCloudBuildProperties.Account()
account.setName(name)
account.setProject('blah')
return account
}

void 'is able to get a list of jenkins buildMasters'() {
given:
createMocks(['master2': null, 'build.buildServices.blah': null, 'master1': null])
Expand All @@ -98,6 +112,51 @@ class InfoControllerSpec extends Specification {
response.contentAsString == '["build.buildServices.blah","master1","master2"]'
}

void 'is able to get a list of google cloud build accounts'() {
given:
createMocks([:], [createGCBAccount("account1"), createGCBAccount("account2")])

when:
MockHttpServletResponse response = mockMvc.perform(get('/buildServices')
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
def actualAccounts = new JsonSlurper().parseText(response.contentAsString).collect { it.name };
['account1', 'account2'] == actualAccounts

}

void 'buildServices return empty permissions list if no permisions have been defined'() {
given:
createMocks([:], [createGCBAccount("account1")])

when:
MockHttpServletResponse response = mockMvc.perform(get('/buildServices')
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
def actualAccounts = new JsonSlurper().parseText(response.contentAsString);
actualAccounts.size == 1
actualAccounts[0].permissions == [:]

}

void 'buildServices returns correctly if gcb is not defined'() {
given:
JenkinsService jenkinsService1 = new JenkinsService('master2', null, false, Permissions.EMPTY)
createMocks(['master2': jenkinsService1])

when:
MockHttpServletResponse response = mockMvc.perform(get('/buildServices')
.accept(MediaType.APPLICATION_JSON)).andReturn().response

then:
def actualAccounts = new JsonSlurper().parseText(response.contentAsString);
actualAccounts.size == 1
actualAccounts[0].name == 'master2'

}

void 'is able to get a list of buildServices with permissions'() {
given:
JenkinsService jenkinsService1 = new JenkinsService('jenkins-foo', null, false, Permissions.EMPTY)
Expand All @@ -109,11 +168,18 @@ class InfoControllerSpec extends Specification {
new Permissions.Builder()
.add(Authorization.READ, ['group-3', 'group-4'])
.add(Authorization.WRITE, 'group-3').build(), false)

GoogleCloudBuildProperties.Account gcbAccount = createGCBAccount('gcbAccount');
gcbAccount.setPermissions(new Permissions.Builder()
.add(Authorization.READ, ['group-5', 'group-6'])
.add(Authorization.WRITE, ['group-5'])
)

createMocks([
'jenkins-foo': jenkinsService1,
'jenkins-bar': jenkinsService2,
'travis-baz': travisService
])
], [gcbAccount])

when:
MockHttpServletResponse response = mockMvc.perform(get('/buildServices')
Expand Down Expand Up @@ -156,6 +222,18 @@ class InfoControllerSpec extends Specification {
"group-3"
]
}
},
{ "name": "gcbAccount",
"buildServiceProvider": "GCB",
"permissions": {
"READ": [
"group-5",
"group-6"
],
"WRITE": [
"group-5"
]
}
}
]
Expand Down

0 comments on commit 486b26a

Please sign in to comment.