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: IAS App-to-Service Communication #331

Merged
merged 21 commits into from
Mar 8, 2024
Merged

Conversation

Johannes-Schneider
Copy link
Contributor

@Johannes-Schneider Johannes-Schneider commented Feb 27, 2024

Context

SAP/cloud-sdk-java-backlog#373.

This PR adds a new ServiceBindingDestinationLoader implementation that is capable of dealing with (currently) imaginary service bindings from re-use services that use IAS for authentication.

The suggested Service Binding format looks like this:

{
  "credentials": {
    "authentication-service": {
      "service-label": "identity", // mandatory
      "app-name": "my-app-name" // optional
    },
    "endpoints": {
      "some-endpoint": {
        "protocol": "http", // optional, "http" by default
        "uri": "http://some-endpoint.com", // mandatory for "http" protocol
        "always-requires-token": true, // optional, defaults to true
        "requires-mtls": true // optional, defaults to true
      }
    }
  }
}

Feature scope:

  • Assume and parse a service binding format

Definition of Done

  • Functionality scope stated & covered
  • Tests cover the scope above
  • Error handling created / updated & covered by the tests above
  • Documentation updated
  • Release notes updated

@Johannes-Schneider Johannes-Schneider added do not merge Pull request must not be merged please review Request to review a pull request labels Feb 27, 2024
Copy link
Contributor

@newtork newtork left a comment

Choose a reason for hiding this comment

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

Progress looks good. I'm only concerned about

  1. customizability (for future JSON format changes)
  2. testability (for current event-broker JSON format)

return delegateLoader.tryGetDestination(optionsBuilder.build());
}

private static boolean isIdentityAuthenticationServiceBinding( @Nonnull final ServiceBinding serviceBinding )
Copy link
Contributor

Choose a reason for hiding this comment

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

(Comment)

I think this is fine for now. I wonder whether we'll need to make this an accessible / modifiable Predicate<ServiceBinding> instead in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As we are working together with the Event Broker colleagues (who are taking a front runner position in the entire IAS topic) to define a standard format for IAS-backed services, I'd hope that we can get around further configuration options in this implementation.

In other words: Services either adhere to our standard or won't be supported by the SDK. As we are the de-facto only library (AFAIK) in the entire SAP ecosystem to support IAS connectivity, I feel we are in a very strong position to "force" our standard onto service providers.

Nevertheless, I don't see any blockers for future extensibility of our implementation in case there are (once again) varying service binding formats.

@@ -1 +1,2 @@
com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2ServiceBindingDestinationLoader
com.sap.cloud.sdk.cloudplatform.connectivity.IdentityAuthenticationServiceBindingDestinationLoader
Copy link
Contributor

Choose a reason for hiding this comment

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

(Minor)

Until we have a JSON syntax aligned, let's disable the auto loading.

Copy link
Member

Choose a reason for hiding this comment

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

Any downside to having it loaded? I would argue the matching criteria of having an entry("authentication-service.service-label", "identity") so so specific it is basically impossible to match it by accident to some existing binding..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initially, Alex and I discussed whether it would be a good idea to auto load this new implementation into all Cloud SDK applications without having a (somewhat) aligned standard for the Service Binding format.

However, as we further refined how we think those service bindings should look like, I'd agree that it doesn't really hurt to already load this new implementation.

Comment on lines 85 to 87
if( !endpoint.requiresTokenForTechnicalAccess
&& options.getOnBehalfOf() != OnBehalfOf.NAMED_USER_CURRENT_TENANT ) {
optionsBuilder.withOption(BtpServiceOptions.IasOptions.withMutualTlsOnly());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm wondering whether this is correct for the "current Tenant is subscriber" case... Probably not, right?

So it should be

Suggested change
if( !endpoint.requiresTokenForTechnicalAccess
&& options.getOnBehalfOf() != OnBehalfOf.NAMED_USER_CURRENT_TENANT ) {
optionsBuilder.withOption(BtpServiceOptions.IasOptions.withMutualTlsOnly());
if( !endpoint.requiresTokenForTechnicalAccess
&& options.getOnBehalfOf() == OnBehalfOf.TECHNICAL_USER_PROVIDER ) {
optionsBuilder.withOption(BtpServiceOptions.IasOptions.withMutualTlsOnly());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed, this implementation has been shifted towards the OAuth2PropertySupplier as this class (the IdentityAuthenticationServiceBindingDestinationLoader) cannot determine whether the current tenant is the provider.

Also see the changes for within the IasOptions class for reference.

Comment on lines 102 to 109
if( bindingView.endpoints.size() > 1 ) {
log
.warn(
"The IAS-based service binding for service '{}' contains multiple HTTP endpoints. Only the first one will be used.",
bindingView.serviceIdentifier);
}

return bindingView.endpoints.get(0);
Copy link
Contributor Author

@Johannes-Schneider Johannes-Schneider Feb 29, 2024

Choose a reason for hiding this comment

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

Discussion

Should we already introduce some API for customers to select the endpoint they want? Or do we wait until we actually encounter a feature request?

Copy link
Contributor

@newtork newtork Feb 29, 2024

Choose a reason for hiding this comment

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

(Comment)

CAP reminded us; first element may not necessarily be consistent in JSON format.
Even in Java-case having LinkedHashMap as deserialization object (maybe) it may not be consistent in service binding side.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed: We are now throwing an DestinationAccessException if there is not exactly one HTTP endpoint available.
This behavior will be changed when we introduce new API that lets customers pick the endpoint they want to connect to.

@Johannes-Schneider Johannes-Schneider marked this pull request as ready for review March 1, 2024 08:36
@Johannes-Schneider Johannes-Schneider added please merge Request to merge a pull request and removed do not merge Pull request must not be merged labels Mar 1, 2024
Comment on lines 157 to 160
catch( final ValueCastException e ) {
// the `service-label` entry is not a string
return false;
}
Copy link
Contributor

@newtork newtork Mar 6, 2024

Choose a reason for hiding this comment

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

(Minor)

Please don't swallow exceptions, without a trace/debug log statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd argue that it doesn't provide any value if we were to log the exception here since it is a valid outcome that the authentication-service.service-label is not a String (even though the chances are pretty minimal).

Unfortunately, we don't have any API within the SBL to check whether a field is of a certain type so that we have to rely on throwing and catching exception for the moment.
Personally, I'd be happy to introduce such new API, but I don't know if the benefit justifies the effort.

@@ -1 +1,2 @@
com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2ServiceBindingDestinationLoader
com.sap.cloud.sdk.cloudplatform.connectivity.IdentityAuthenticationServiceBindingDestinationLoader
Copy link
Member

Choose a reason for hiding this comment

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

Any downside to having it loaded? I would argue the matching criteria of having an entry("authentication-service.service-label", "identity") so so specific it is basically impossible to match it by accident to some existing binding..

}

try {
return credentials.getMapView("authentication-service");
Copy link
Member

Choose a reason for hiding this comment

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

Feels a bit bad to have around 200 LoC just for loading a few JSON fields.. Personally I'd probably wrap some of this into a getOrDefault(str, default) method, but not sure how much better that looks..

Copy link
Member

Choose a reason for hiding this comment

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

Or we directly replace this with gson or jackson deserialization maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Feels a bit bad to have around 200 LoC just for loading a few JSON fields

Agreed. Let me see what I can do about it.


import io.vavr.control.Try;

@Isolated( "because the tests manipulate the global default ServiceBindingAccessor" )
Copy link
Member

Choose a reason for hiding this comment

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

👌🏼

.containsExactly(URI.create("https://foo.uri"));
assertThat(delegateOptions.getOption(BtpServiceOptions.IasOptions.NoTokenForTechnicalProviderAccess.class))
.contains(true);
assertThat(delegateOptions.getOption(BtpServiceOptions.IasOptions.IasCommunicationOptions.class)).isEmpty();
Copy link
Member

Choose a reason for hiding this comment

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

these assertions are the same as for the case above, right? If so, what does this test really assert, other than the behalf not impacting the assertions? Maybe we can reduce the code duplication between these test cases still a bit to make it clearer what the differences are between them?

Copy link
Member

@MatKuhr MatKuhr left a comment

Choose a reason for hiding this comment

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

lgtm 👍🏻

@MatKuhr MatKuhr merged commit 2d9b1ee into main Mar 8, 2024
16 checks passed
@MatKuhr MatKuhr deleted the feat/ias-app-to-service branch March 8, 2024 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
please merge Request to merge a pull request please review Request to review a pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants