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

14601 authorization api #16495

Merged
merged 40 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d530487
15864 Spring Cloud Gate configuration with Swagger
jalbinson Oct 21, 2024
e68207f
Merge branch 'master' into platform/jamie/15864-spring-cloud-swagger
jalbinson Oct 21, 2024
3403b45
add basic healthcheck test
jalbinson Oct 21, 2024
cbe8404
Merge branch 'platform/jamie/15864-spring-cloud-swagger' of github.co…
jalbinson Oct 21, 2024
8e64119
Merge branch 'master' into platform/jamie/15864-spring-cloud-swagger
jalbinson Oct 21, 2024
74046c6
Merge branch 'master' into platform/jamie/15864-spring-cloud-swagger
jalbinson Oct 22, 2024
9d0dc6f
PR feedback
jalbinson Oct 22, 2024
2986d9c
Merge branch 'master' into platform/jamie/15864-spring-cloud-swagger
jalbinson Oct 22, 2024
652a45e
Merge branch 'platform/jamie/15864-spring-cloud-swagger' of github.co…
jalbinson Oct 22, 2024
a94be0b
config injection cleanup
jalbinson Oct 22, 2024
74a903a
Merge branch 'master' into platform/jamie/15864-spring-cloud-swagger
jalbinson Oct 22, 2024
c093548
fix test
jalbinson Oct 22, 2024
ceea1f7
Merge branch 'platform/jamie/15864-spring-cloud-swagger' of github.co…
jalbinson Oct 22, 2024
df7bac8
14601 authz api
jalbinson Nov 6, 2024
da94858
merge main
jalbinson Nov 6, 2024
c000475
fix merge issue
jalbinson Nov 6, 2024
9ea32e5
comments
jalbinson Nov 6, 2024
c9c2ba7
Merge branch 'main' into platform/jamie/14601-authz-api
emvaldes Nov 7, 2024
e24d2a8
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 7, 2024
6ddc157
doc
jalbinson Nov 7, 2024
77efd61
Merge branch 'platform/jamie/14601-authz-api' of github.com:CDCgov/pr…
jalbinson Nov 7, 2024
75862cc
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 7, 2024
74d025d
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 12, 2024
d290bc7
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 13, 2024
1ace3d1
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 14, 2024
3cb1171
Update auth/docs/setup.md
jalbinson Nov 19, 2024
54fb1ba
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 19, 2024
85abc4a
PR review
jalbinson Nov 19, 2024
a60f074
Merge branch 'platform/jamie/14601-authz-api' of github.com:CDCgov/pr…
jalbinson Nov 19, 2024
f0bf94f
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 21, 2024
dad9f2c
latest version of okta SDK after fix
jalbinson Nov 22, 2024
60f59f7
Merge branch 'platform/jamie/14601-authz-api' of github.com:CDCgov/pr…
jalbinson Nov 22, 2024
6d902c7
revert spring boot update
jalbinson Nov 22, 2024
bd308f4
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Nov 25, 2024
3d6faf3
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Dec 2, 2024
f548a03
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Dec 3, 2024
61b9a8f
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Dec 4, 2024
e5fa576
various changes required for spring boot 3.4.0 on both auth and submi…
jalbinson Dec 4, 2024
494135e
Merge branch 'platform/jamie/14601-authz-api' of github.com:CDCgov/pr…
jalbinson Dec 4, 2024
b1339ae
Merge branch 'main' into platform/jamie/14601-authz-api
jalbinson Dec 5, 2024
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
12 changes: 11 additions & 1 deletion auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@ dependencies {
* Spring WebFlux was chosen for this project to be able to better handle periods of high traffic
*/
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.cloud:spring-cloud-gateway-webflux")
implementation("org.springframework.cloud:spring-cloud-starter-gateway")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")

runtimeOnly("com.nimbusds:oauth2-oidc-sdk:11.19.1")

// okta
// Do not update to 19.0.0 due to breaking issue
// downgraded to 18.0.0 for refresh capability
implementation("com.okta.sdk:okta-sdk-api:18.0.0")
runtimeOnly("com.okta.sdk:okta-sdk-impl:18.0.0")

// Swagger
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.6.0")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package gov.cdc.prime.reportstream.auth

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication

@SpringBootApplication
@ConfigurationPropertiesScan
class AuthApplication

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ package gov.cdc.prime.reportstream.auth
object AuthApplicationConstants {

/**
* All endpoints defined here
* All Auth service endpoints defined here
*/
object Endpoints {
const val HEALTHCHECK_ENDPOINT_V1 = "/api/v1/healthcheck"
}

object Scopes {
const val ORGANIZATION_SCOPE = "organization"
const val SUBJECT_SCOPE = "sub"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gov.cdc.prime.reportstream.auth.client

import com.okta.sdk.resource.api.ApplicationGroupsApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.logging.log4j.kotlin.Logging
import org.springframework.stereotype.Service

@Service
class OktaGroupsClient(
private val applicationGroupsApi: ApplicationGroupsApi,
) : Logging {

/**
* Get all application groups from the Okta Admin API
*
* Group names are found at json path "_embedded.group.profile.name"
*
* @see https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/#tag/ApplicationGroups/operation/listApplicationGroupAssignments
*/
suspend fun getApplicationGroups(appId: String): List<String> {
return withContext(Dispatchers.IO) {
try {
applicationGroupsApi
.listApplicationGroupAssignments(appId, null, null, null, "group")
.map { it.embedded?.get("group") as Map<*, *> }
.map { it["profile"] as Map<*, *> }
.map { it["name"] as String }
} catch (ex: Exception) {
logger.error("Error retrieving application groups from Okta API", ex)
throw ex
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
package gov.cdc.prime.reportstream.auth.config

import gov.cdc.prime.reportstream.auth.model.Environment
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Clock
import kotlin.time.TimeSource

/**
* Simple class to automatically read configuration from application.yml (or environment variable overrides)
*/
@Configuration
@EnableConfigurationProperties(ProxyConfigurationProperties::class)
class ApplicationConfig(
val proxyConfig: ProxyConfigurationProperties,
@ConfigurationProperties(prefix = "app")
data class ApplicationConfig(
val environment: Environment,
) {

@Bean
fun timeSource(): TimeSource {
return TimeSource.Monotonic
}
}

@ConfigurationProperties("proxy")
data class ProxyConfigurationProperties(
val pathMappings: List<ProxyPathMapping>,
)

data class ProxyPathMapping(
val baseUrl: String,
val pathPrefix: String,
)
@Bean
fun clock(): Clock {
return Clock.systemUTC()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gov.cdc.prime.reportstream.auth.config

import com.okta.sdk.client.AuthorizationMode
import com.okta.sdk.client.Clients
import com.okta.sdk.resource.api.ApplicationGroupsApi
import com.okta.sdk.resource.client.ApiClient
import gov.cdc.prime.reportstream.shared.StringUtilities.base64Decode
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class OktaClientConfig(
private val oktaClientProperties: OktaClientProperties,
) {

@Bean
fun apiClient(): ApiClient {
return Clients.builder()
.setOrgUrl(oktaClientProperties.orgUrl)
.setAuthorizationMode(AuthorizationMode.PRIVATE_KEY)
.setClientId(oktaClientProperties.clientId)
.setScopes(oktaClientProperties.requiredScopes)
.setPrivateKey(oktaClientProperties.apiPrivateKey)
// .setCacheManager(...) TODO: investigate caching since groups don't often change
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a ticket for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just leave this be for now. Caching is something we can worry about if performance becomes a problem especially since this is currently a POC.

.build()
}

@Bean
fun applicationGroupsApi(): ApplicationGroupsApi {
return ApplicationGroupsApi(apiClient())
}

@ConfigurationProperties(prefix = "okta.admin-client")
data class OktaClientProperties(
val orgUrl: String,
val clientId: String,
val requiredScopes: Set<String>,
private val apiEncodedPrivateKey: String,
) {
// PEM encoded format
val apiPrivateKey = apiEncodedPrivateKey.base64Decode()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gov.cdc.prime.reportstream.auth.config

import gov.cdc.prime.reportstream.auth.AuthApplicationConstants
import gov.cdc.prime.reportstream.auth.model.Environment
import org.apache.logging.log4j.kotlin.Logging
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
Expand All @@ -14,17 +16,27 @@ import org.springframework.security.web.server.SecurityWebFilterChain
*/
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
class SecurityConfig(
private val applicationConfig: ApplicationConfig,
) : Logging {

@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http
.csrf { it.disable() } // TODO: re-enable after 16312
.authorizeExchange { authorize ->
authorize
// allow health endpoint without authentication
.pathMatchers(AuthApplicationConstants.Endpoints.HEALTHCHECK_ENDPOINT_V1).permitAll()
// all other requests must be authenticated
.anyExchange().authenticated()

// allow unauthenticated access to swagger on local environments
if (applicationConfig.environment == Environment.LOCAL) {
logger.info("Allowing unauthenticated Swagger access at http://localhost:9000/swagger/ui.html")
authorize.pathMatchers("/swagger/**").permitAll()
}

// all other requests must be authenticated
authorize.anyExchange().authenticated()
}
.oauth2ResourceServer {
it.opaqueToken { }
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gov.cdc.prime.reportstream.auth.filter

import gov.cdc.prime.reportstream.auth.AuthApplicationConstants
import gov.cdc.prime.reportstream.auth.service.OktaGroupsService
import gov.cdc.prime.reportstream.shared.auth.jwt.OktaGroupsJWTConstants
import kotlinx.coroutines.reactor.mono
import org.springframework.cloud.gateway.filter.GatewayFilter
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono

/**
* This filter defines how the Okta-Groups header is added to requests
*/
@Component
class AppendOktaGroupsGatewayFilterFactory(
private val oktaGroupsService: OktaGroupsService,
) : AbstractGatewayFilterFactory<Any>() {

/**
* function used only in testing to create our filter without any configuration
*/
fun apply(): GatewayFilter {
return apply { _: Any? -> }
}

override fun apply(config: Any?): GatewayFilter {
return GatewayFilter { exchange, chain ->
exchange
.getPrincipal<BearerTokenAuthentication>()
.flatMap { oktaAccessTokenJWT ->
val appId = oktaAccessTokenJWT
.tokenAttributes[AuthApplicationConstants.Scopes.SUBJECT_SCOPE] as String
val organizations = oktaAccessTokenJWT
.tokenAttributes[AuthApplicationConstants.Scopes.ORGANIZATION_SCOPE] as List<*>?

// If there is no organization claim present, then we have an application user and
// require appending our custom header
if (organizations == null) {
mono { oktaGroupsService.generateOktaGroupsJWT(appId) }
} else {
Mono.empty()
}
}
.map { oktaGroupsJWT: String ->
exchange.request
.mutate()
.headers {
it.add(OktaGroupsJWTConstants.OKTA_GROUPS_HEADER, oktaGroupsJWT)
}
.build()
}
.switchIfEmpty(Mono.just(exchange.request)) // drop back in original unmodified request if not an app
.flatMap { request ->
chain.filter(exchange.mutate().request(request).build())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gov.cdc.prime.reportstream.auth.model

/**
* All possible environments the auth app can be running
*/
enum class Environment {
LOCAL,
STAGING,
PRODUCTION,
}
Loading