Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gcb): Add permissioning to Google Cloud Build accounts #523

Merged
merged 2 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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