Skip to content

Commit

Permalink
Implement SBOM actuator endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mhalbritter committed Feb 28, 2024
1 parent bfc9ef8 commit a10b562
Show file tree
Hide file tree
Showing 30 changed files with 22,013 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[[sbom]]
= Software Bill of Materials (`sbom`)
The `sbom` endpoint provides information about the software bill of materials (SBOM).



[[sbom.retrieving-available-sboms]]
== Retrieving the available SBOMs
To retrieve the available SBOMs, make a `GET` request to `/actuator/sbom`, as shown in the following curl-based example:

include::{snippets}/sbom/curl-request.adoc[]

The resulting response is similar to the following:

include::{snippets}/sbom/http-response.adoc[]



[[sbom.retrieving-available-sboms.response-structure]]
=== Response Structure
The response contains the available SBOMs.
The following table describes the structure of the response:

[cols="2,1,3"]
include::{snippets}/sbom/response-fields.adoc[]



[[sbom.retrieving-single-sbom]]
== Retrieving a single SBOM

To retrieve the available SBOMs, make a `GET` request to `/actuator/sbom/\{id}`, as shown in the following curl-based example:

include::{snippets}/sbom/id/curl-request.adoc[]

The preceding example retrieves the SBOM named application.
The resulting response depends on the format of the SBOM.
This example uses the CycloneDX format.

[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: application/vnd.cyclonedx+json
Accept-Ranges: bytes
Content-Length: 160316
{
"bomFormat" : "CycloneDX",
"specVersion" : "1.5",
"serialNumber" : "urn:uuid:13862013-3360-43e5-8055-3645aa43c548",
"version" : 1,
// ...
}
----



[[sbom.retrieving-single-sbom.response-structure]]
=== Response Structure
The response depends on the format of the SBOM:

* https://cyclonedx.org/specification/overview/[CycloneDX]

Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ include::endpoints/prometheus.adoc[leveloffset=+1]

include::endpoints/quartz.adoc[leveloffset=+1]

include::endpoints/sbom.adoc[leveloffset=+1]

include::endpoints/scheduledtasks.adoc[leveloffset=+1]

include::endpoints/sessions.adoc[leveloffset=+1]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.sbom;

import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.sbom.SbomEndpoint;
import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension;
import org.springframework.boot.actuate.sbom.SbomProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ResourceLoader;

/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link SbomEndpoint}.
*
* @author Moritz Halbritter
* @since 3.3.0
*/
@AutoConfiguration
@ConditionalOnAvailableEndpoint(endpoint = SbomEndpoint.class)
@EnableConfigurationProperties(SbomProperties.class)
public class SbomEndpointAutoConfiguration {

private final SbomProperties properties;

SbomEndpointAutoConfiguration(SbomProperties properties) {
this.properties = properties;
}

@Bean
@ConditionalOnMissingBean
SbomEndpoint sbomEndpoint(ResourceLoader resourceLoader) {
return new SbomEndpoint(this.properties, resourceLoader);
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(SbomEndpoint.class)
@ConditionalOnAvailableEndpoint(exposure = EndpointExposure.WEB)
SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint sbomEndpoint) {
return new SbomEndpointWebExtension(sbomEndpoint, this.properties);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Auto-configuration for actuator SBOM concerns.
*/
package org.springframework.boot.actuate.autoconfigure.sbom;
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ org.springframework.boot.actuate.autoconfigure.r2dbc.ConnectionFactoryHealthCont
org.springframework.boot.actuate.autoconfigure.r2dbc.R2dbcObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.data.redis.RedisHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.data.redis.RedisReactiveHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.sbom.SbomEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksObservabilityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;

import org.junit.jupiter.api.Test;

import org.springframework.boot.actuate.sbom.SbomEndpoint;
import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension;
import org.springframework.boot.actuate.sbom.SbomProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ResourceLoader;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;

import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* Tests for generating documentation describing the {@link SbomEndpoint}.
*
* @author Moritz Halbritter
*/
class SbomEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {

@Test
void sbom() throws Exception {
this.mockMvc.perform(get("/actuator/sbom"))
.andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("sbom",
responseFields(fieldWithPath("ids").description("An array of available SBOM ids."))));
}

@Test
void sboms() throws Exception {
this.mockMvc.perform(get("/actuator/sbom/application"))
.andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("sbom/id"));
}

@Configuration(proxyBeanMethods = false)
@Import(BaseDocumentationConfiguration.class)
static class TestConfiguration {

@Bean
SbomProperties sbomProperties() {
SbomProperties properties = new SbomProperties();
properties.getApplication().setLocation("classpath:sbom/cyclonedx.json");
return properties;
}

@Bean
SbomEndpoint endpoint(SbomProperties properties, ResourceLoader resourceLoader) {
return new SbomEndpoint(properties, resourceLoader);
}

@Bean
SbomEndpointWebExtension sbomEndpointWebExtension(SbomEndpoint endpoint, SbomProperties properties) {
return new SbomEndpointWebExtension(endpoint, properties);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.sbom;

import org.junit.jupiter.api.Test;

import org.springframework.boot.actuate.sbom.SbomEndpoint;
import org.springframework.boot.actuate.sbom.SbomEndpointWebExtension;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link SbomEndpointAutoConfiguration}.
*
* @author Moritz Halbritter
*/
class SbomEndpointAutoConfigurationTests {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SbomEndpointAutoConfiguration.class));

@Test
void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sbom")
.run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class));
}

@Test
void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SbomEndpoint.class));
}

@Test
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.sbom.enabled:false")
.withPropertyValues("management.endpoints.web.exposure.include=*")
.run((context) -> assertThat(context).doesNotHaveBean(SbomEndpoint.class));
}

@Test
void runWhenOnlyExposedOverJmxShouldHaveEndpointBeanWithoutWebExtension() {
this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=info", "spring.jmx.enabled=true",
"management.endpoints.jmx.exposure.include=sbom")
.run((context) -> assertThat(context).hasSingleBean(SbomEndpoint.class)
.doesNotHaveBean(SbomEndpointWebExtension.class));
}

}
Loading

0 comments on commit a10b562

Please sign in to comment.