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

Add SNI support to embedded web server SSL auto-configuration #26022

Closed
sokomishalov opened this issue Apr 12, 2021 · 8 comments
Closed

Add SNI support to embedded web server SSL auto-configuration #26022

sokomishalov opened this issue Apr 12, 2021 · 8 comments
Assignees
Labels
theme: ssl Issues related to ssl support type: enhancement A general enhancement
Milestone

Comments

@sokomishalov
Copy link
Contributor

sokomishalov commented Apr 12, 2021

It would be great and useful to have such functionality out-of-box.
I'm not sure if all web-server implementations support SNI, but tomcat and reactor-netty definitely support it.

@sokomishalov sokomishalov changed the title Provide SNI functionality for ssl autoconfiguration Provide SNI functionality for SSL autoconfiguration Apr 12, 2021
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Apr 12, 2021
@wilkinsona
Copy link
Member

Thanks for the suggestion, @sokomishalov. How would you expect to configure the auto-configuration if it supported SNI? I'm wondering what you had imagined the configuration properties would be so that you can provide multiple key stores and map each to a host pattern.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Apr 12, 2021
@sokomishalov
Copy link
Contributor Author

sokomishalov commented Apr 12, 2021

Thanks for the quick reply, @wilkinsona!
Well, I suppose it could look like that:

server:
  ssl:
    enabled: true
    key-store: /path/to/key-store
    key-store-password: foobar
    key-alias: fallback
    sni:
      - mapping: foo.example.com
        ssl:
          key-store: ${server.ssl.key-store}
          key-store-password: ${server.ssl.key-store-password}
          key-alias: foo
      - mapping: bar.example.com
        ssl:
          key-store: ${server.ssl.key-store}
          key-store-password: ${server.ssl.key-store-password}
          key-alias: bar

If you'd want to use SNI, you still have to set up a fallback SSL context in that rare case when the user's client does not support SNI.
I'm not sure if it's ok to reuse SSL properties in that recursive way or just to provide new higher-level property for SNI mappings inside ServerProperties, but I think it could be possible to reuse Ssl-properties POJO somehow.
Also, maybe it could be useful to provide a property that will automatically extract DNS/IP from SAN or just CN to provide default SNI-mapping for this SSL context.
Here is my gist - netty customizer to create SNI from a single key-store with multiple aliases with current spring-boot SSL properties, which maps SSL context to domain names from SAN and/or CN. Maybe someone will find it useful.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Apr 12, 2021
@wilkinsona wilkinsona added type: enhancement A general enhancement theme: ssl Issues related to ssl support and removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Apr 14, 2021
@wilkinsona wilkinsona added this to the 2.x milestone Apr 14, 2021
@wilkinsona

This comment has been minimized.

@SidneyLann

This comment has been minimized.

@wilkinsona

This comment has been minimized.

@SidneyLann
Copy link

When will be supported then?

@snicoll
Copy link
Member

snicoll commented Jan 30, 2022

@SidneyLann There's no commitment to the next feature release 2.7.x as the milestone on the issue indicates (2.x at the moment). If someone contributes a PR, we might get to it sooner.

@SidneyLann
Copy link

SidneyLann commented Oct 15, 2023

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.WebServerSslBundle;
import org.springframework.stereotype.Component;

import com.pcng.gateway.handler.SniSslServerCustomizer;

@Component
public class NettyServerSniCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
  private static final String KEY_STORE = ".pc.com.p12";
  private static final String SSL_FOLDER = "/home/sidney/app/ssl/";
  @Value("${server.http2.enabled}")
  private boolean enableHttp2;

  @Override
  public void customize(NettyReactiveWebServerFactory serverFactory) {
    String domain2s = System.getProperty("domain2s");
    String[] domain2sArr = domain2s.split(",");

    String[] hostNames = new String[domain2sArr.length];
    Ssl.ClientAuth[] clientAuths = new Ssl.ClientAuth[domain2sArr.length];
    Http2[] http2s = new Http2[domain2sArr.length];
    SslBundle[] sslBundles = new SslBundle[domain2sArr.length];

    for (int i = 0; i < domain2sArr.length; i++) {
      hostNames[i] = domain2sArr[i] + ".pc.com";

      Ssl ssl = new Ssl();
      ssl.setKeyStore(SSL_FOLDER + domain2sArr[i] + KEY_STORE);
      ssl.setKeyStorePassword("mypassw0rd");
      ssl.setKeyStoreType("PKCS12");
      ssl.setClientAuth(Ssl.ClientAuth.NONE);

      clientAuths[i] = ssl.getClientAuth();

      Http2 http2 = new Http2();
      http2.setEnabled(enableHttp2);
      http2s[i] = http2;

      sslBundles[i] = WebServerSslBundle.get(ssl);
    }

    try {
      serverFactory.addServerCustomizers(new SniSslServerCustomizer(hostNames, http2s, clientAuths, sslBundles));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;

import io.netty.handler.ssl.ClientAuth;
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.Http2SslContextSpec;
import reactor.netty.http.server.HttpServer;
import reactor.netty.tcp.AbstractProtocolSslContextSpec;
import reactor.netty.tcp.SslProvider;

public class SniSslServerCustomizer implements NettyServerCustomizer {
  private final String[] hostNames;
  private final Http2[] http2;
  private final Ssl.ClientAuth[] clientAuth;
  private final SslBundle[] sslBundle;

  public SniSslServerCustomizer(String[] hostNames, Http2[] http2, Ssl.ClientAuth[] clientAuth, SslBundle[] sslBundle) {
    this.hostNames = hostNames;
    this.http2 = http2;
    this.clientAuth = clientAuth;
    this.sslBundle = sslBundle;
  }

  @Override
  public HttpServer apply(HttpServer server) {
    try {
      AbstractProtocolSslContextSpec<?> sslContextSpec = null;
      Map<String, Consumer<? super SslProvider.SslContextSpec>> domainMap = new HashMap<>();

      for (int i = 1; i < sslBundle.length; i++) {
        sslContextSpec = createSslContextSpec(i);
        final AbstractProtocolSslContextSpec<?> sslContextSpec2 = sslContextSpec;
        domainMap.put(this.hostNames[i], spec -> spec.sslContext(sslContextSpec2));
      }

      return server.secure(spec -> spec.sslContext(createSslContextSpec(0)).addSniMappings(domainMap));
    } catch (Exception e) {
      return null;
    }
  }

  protected AbstractProtocolSslContextSpec<?> createSslContextSpec(int i) {
    AbstractProtocolSslContextSpec<?> sslContextSpec = (this.http2[i] != null && this.http2[i].isEnabled()) ? Http2SslContextSpec.forServer(this.sslBundle[i].getManagers().getKeyManagerFactory())
        : Http11SslContextSpec.forServer(this.sslBundle[i].getManagers().getKeyManagerFactory());
    sslContextSpec.configure((builder) -> {
      builder.trustManager(this.sslBundle[i].getManagers().getTrustManagerFactory());
      SslOptions options = this.sslBundle[i].getOptions();
      builder.protocols(options.getEnabledProtocols());
      builder.ciphers(SslOptions.asSet(options.getCiphers()));
      builder.clientAuth(org.springframework.boot.web.server.Ssl.ClientAuth.map(this.clientAuth[i], ClientAuth.NONE, ClientAuth.OPTIONAL, ClientAuth.REQUIRE));
    });
    
    return sslContextSpec;
  }
}

I have a spring boot 3 workaround now.

@scottfrederick scottfrederick self-assigned this Feb 17, 2024
@scottfrederick scottfrederick changed the title Provide SNI functionality for SSL autoconfiguration Add SNI support to embedded web server SSL auto-configuration Mar 27, 2024
@scottfrederick scottfrederick modified the milestones: 3.x, 3.3.0-RC1 Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: ssl Issues related to ssl support type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

7 participants