Применение TLS к рабочему входу приводит к бэкенду по умолчанию - 404 - PullRequest
3 голосов
/ 22 марта 2020

Я установил Nginx Ingress Controller через helm в входном пространстве имен.

helm ls --namespace ingress
NAME            NAMESPACE   REVISION    UPDATED                                 STATUS      CHART                   APP VERSION
nginx-ingress   ingress     1           2020-03-15 10:47:51.143159 +0530 IST    deployed    nginx-ingress-1.34.2    0.30.0  

Служба и развертывание выглядят следующим образом

apiVersion: v1
kind: Service
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: '"true"'
spec:
  type: LoadBalancer
  ports:
    - port: 8080
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: test-service
    app.kubernetes.io/instance: test-service
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: test-service
      app.kubernetes.io/instance: test-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: test-service
        app.kubernetes.io/instance: test-service
    spec:
      containers:
        - name: test-service
          image: "<acr-url>/test-service:c93c58c0bd4918de06d46381a89b293087262cf9"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /devops/health/liveness
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          readinessProbe:
            httpGet:
              path: /devops/health/readiness
              port: 8080
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          env:
            - name: test-serviceClientId
              valueFrom:
                secretKeyRef:
                  key: test-serviceClientId
                  name: test-service-133
            - name: test-serviceClientSecret
              valueFrom:
                secretKeyRef:
                  key: test-serviceClientSecret
                  name: test-service-133
            - name: test-serviceTenantClientId
              valueFrom:
                secretKeyRef:
                  key: test-serviceTenantClientId
                  name: test-service-133
            - name: test-serviceTenantClientSecret
              valueFrom:
                secretKeyRef:
                  key: test-serviceTenantClientSecret
                  name: test-service-133
          resources:
            limits:
              cpu: 800m
            requests:
              cpu: 300m

Настроил вход на службу с перезаписью следующим образом

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  tls:
  - hosts:
    - apiexample.centralus.cloudapp.azure.com
    secretName: tls-secret
  rules:
    - host: "apiexample.centralus.cloudapp.azure.com"
      http:
        paths:
          - path: /testservice(/|$)(.*)
            backend:
              serviceName: test-service
              servicePort: 8080

tls-secret был сгенерирован с использованием

$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=apiexample.centralus.cloudapp.azure.com/O=apiexample.centralus.cloudapp.azure.com"

$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt

До применения конфигурации tls во входе я смог получить ответ от конечной точки API. Конечная точка API защищена с помощью oauth.

Конечная точка API:

http://apiexample.centralus.cloudapp.azure.com/testservice/tenant/api/v1/endpoint

После применения конфигурации TLS на входе и нажатия

https://apiexample.centralus.cloudapp.azure.com/testservice/tenant/api/v1/endpoint

Я получаю значение по умолчанию backend 404.

Я проверил TLS со входом, используя другой пример сервиса (который не защищен с помощью oauth), и, похоже, он работает для этой службы. Вот конфигурация для других служб

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tea
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tea
  template:
    metadata:
      labels:
        app: tea
    spec:
      containers:
      - name: tea
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: tea-svc
  labels:
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: tea

Вход для службы настроен следующим образом

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cafe-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  tls:
  - hosts:
    - apiexample.centralus.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: apiexample.centralus.cloudapp.azure.com
    http:
      paths:
      - path: /teaprefix(/|$)(.*)
        backend:
          serviceName: tea-svc
          servicePort: 80

Конечная точка

https://apiexample.centralus.cloudapp.azure.com/teaprefix/someurl

работает нормально.

Пожалуйста, дайте мне знать, если в моей конфигурации чего-то не хватает и какие-либо потенциальные проблемы, которые я мог бы игнорировать.

Примечание. Служба и входная информация развернуты в пространстве имен по умолчанию и входе Контроллер работает во входном пространстве имен. Входной контроллер Nginx работает в 2 блоках

Журналы с входного контроллера с конфигурацией TLS

Pod1

 10.244.0.1 - - [22/Mar/2020:06:57:12 +0000] "GET /testservice/tenant/api/v1/endpoint HTTP/1.1" 302 0 "-" "PostmanRuntime/7.23.0" 1495 0.004 [default-test-service-8080] [] 10.244.0.7:8080 0 0.004 302 f4671ede2f95148220c21fe44de6fdad
 10.244.0.1 - - [22/Mar/2020:06:57:13 +0000] "GET /tenant/api/v1/endpoint HTTP/1.1" 404 21 "http://apiexample.centralus.cloudapp.azure.com/tenant/api/v1/endpoint" "PostmanRuntime/7.23.0" 1563 0.001 [upstream-default-backend] [] 10.244.0.225:8080 21 0.004 404 ed41b36bc6b89b60bc3f208539a0d44c

Pod2

    10.244.0.1 - - [22/Mar/2020:06:57:12 +0000] "GET /tenant/api/v1/endpoint HTTP/1.1" 308 171 "https://apiexample.centralus.cloudapp.azure.com/testservice/tenant/api/v1/endpoint" "PostmanRuntime/7.23.0" 1580 0.000 [upstream-default-backend] [] - - - - ce955b7bb5118169e99dd4051060c897

Журналы с Ingress Controller без конфигурации TLS

10.244.0.1 - - [22/Mar/2020:07:04:34 +0000] "GET /testservice/tenant/api/v1/endpoint HTTP/1.1" 200 276 "-" "PostmanRuntime/7.23.0" 1495 2.165 [default-test-service-8080] [] 10.244.0.4:8080 548 2.168 200 e866f277def90c398df4e509e45718b2

ОБНОВЛЕНИЕ

Отключение аутентификации в бэкэнд-сервисе (test-service) также приводит к такое же поведение Без применения TLS, может подключиться к конечной точке, используя http без каких-либо токенов на предъявителя. После применения TLS получите бэкэнд по умолчанию - 404, когда я достиг конечной точки с помощью https / http

UPDATE

Предоставление сервиса через ClusterIP без

service.beta.kubernetes.io/azure-load-balancer-internal: '"true"'

аннотация вместо LoadBalancer также, похоже, не помогает. Конечная точка работает без TLS и с примененным TLS, получите бэкэнд по умолчанию - 404

ОБНОВЛЕНИЕ

Служба тестирования представляет собой приложение Spring Boot со следующей WebSecurityConfiguration

@Component
@EnableResourceServer
public class WebSecurityConfiguration extends ResourceServerConfigurerAdapter {

  private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfiguration.class);

  private final HealthCheckWebSecurity healthCheckWebSecurity = new HealthCheckWebSecurity();

  private final Oauth2Settings oauth2Settings;
  private final JwtTokenStore jwtTokenStore;
  private final TenantService tenantService;
  private final TransportGuaranteeWebSecurity transportGuaranteeWebSecurity;

  @Autowired
  public WebSecurityConfiguration(
      Oauth2Settings oauth2Settings,
      JwtTokenStore jwtTokenStore,
      TenantService tenantService,
      TransportGuaranteeWebSecurity transportGuaranteeWebSecurity) {
    this.oauth2Settings = oauth2Settings;
    this.jwtTokenStore = jwtTokenStore;
    this.tenantService = tenantService;
    this.transportGuaranteeWebSecurity = transportGuaranteeWebSecurity;
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    String resourceId = oauth2Settings.getResource("default").getResourceId();
    LOGGER.info("Resource service id: {}", resourceId);

    resources.resourceId(resourceId).tokenStore(jwtTokenStore);
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http.requestMatchers().anyRequest();
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http.csrf().disable();

    healthCheckWebSecurity.configure(http);
    transportGuaranteeWebSecurity.configure(http);

    http.authorizeRequests().anyRequest().permitAll();
    http.addFilterAfter(buildTenancyContextFilter(), ChannelProcessingFilter.class);
    http.addFilterAfter(buildLongUrlFilter(), ChannelProcessingFilter.class);
  }

  private TenancyContextFilter buildTenancyContextFilter() {
    return new TenancyContextFilter(tenantService,
        new PathVariableTenantExtractor(Arrays.asList("/{tenantAlias}/api/**")));
  }

  private LongRequestHttpFilter buildLongUrlFilter() {
    return new LongRequestHttpFilter();
  }
}

public final class TransportGuaranteeWebSecurity {

  private TransportGuaranteeSettings transportGuaranteeSettings;

  TransportGuaranteeWebSecurity(TransportGuaranteeSettings transportGuaranteeSettings) {
    this.transportGuaranteeSettings = transportGuaranteeSettings;
  }


  public void configure(HttpSecurity httpSecurity) throws Exception {
    if (httpsRequired()) {
      httpSecurity.requiresChannel().anyRequest().requiresSecure();
    } else {
      httpSecurity.requiresChannel().anyRequest().requiresInsecure();
    }
  }

  private boolean httpsRequired() {
    final String transportGuarantee = transportGuaranteeSettings.getTransportGuarantee();
    return !TransportGuaranteeSettings.TRANSPORT_GUARANTEE_NONE.equalsIgnoreCase(transportGuarantee);
  }

}


@ConfigurationProperties(prefix = "web.security")
public class TransportGuaranteeSettings {
  static final String TRANSPORT_GUARANTEE_NONE = "NONE";
  static final String TRANSPORT_GUARANTEE_CONFIDENTIAL = "CONFIDENTIAL";

  private static final Logger LOGGER = LoggerFactory.getLogger(TransportGuaranteeSettings.class);

  private static final String TRANSPORT_GUARANTEE_PROPERTY = "web.security.transportGuarantee";

  private String transportGuarantee;

  public String getTransportGuarantee() {
    return transportGuarantee;
  }

  public void setTransportGuarantee(String transportGuarantee) {
    this.transportGuarantee = transportGuarantee.trim();
    logUnexpectedValue();
  }

  private void logUnexpectedValue() {
    if (!TRANSPORT_GUARANTEE_NONE.equalsIgnoreCase(transportGuarantee)
        && !TRANSPORT_GUARANTEE_CONFIDENTIAL.equalsIgnoreCase(transportGuarantee)) {
      LOGGER.debug(
          "Unknown value '{}' for property '{}' (expected '{}' or '{}'). Defaulted to '{}'.",
          transportGuarantee, TRANSPORT_GUARANTEE_PROPERTY, TRANSPORT_GUARANTEE_NONE, TRANSPORT_GUARANTEE_CONFIDENTIAL,
          TRANSPORT_GUARANTEE_CONFIDENTIAL);
    }
  }
}

В моем application.yaml

web.security.transportGuarantee: NONE

Фильтр контекста аренды извлекает информацию об арендаторе из URL и устанавливает ThreadLocal. С этим не должно быть никаких проблем, поскольку я могу подключиться к конечной точке без конфигурации TLS. Я также не вижу проблем с TransportGuaranteeWebSecurity по той же причине.

Еще несколько журналов для отладки

kubectl get pods -owide --namespace ingress

NAME                                             READY   STATUS    RESTARTS   AGE   IP             NODE                                NOMINATED NODE   READINESS GATES
nginx-ingress-controller-5fcbccd545-bdh25        1/1     Running   1          15d   10.244.0.22    aks-agentpool-44086776-vmss000000   <none>           <none>
nginx-ingress-controller-5fcbccd545-ptx6j        1/1     Running   0          15d   10.244.0.21    aks-agentpool-44086776-vmss000000   <none>           <none>
nginx-ingress-default-backend-554d7bd77c-zxzlf   1/1     Running   0          15d   10.244.0.225   aks-agentpool-44086776-vmss000000   <none>           <none>

kubectl get sv c

test-service                          LoadBalancer   10.0.231.35    13.89.111.39    8080:31534/TCP               14d
tea-svc                                   ClusterIP      10.0.12.216    <none>          80/TCP                       17d

kubectl get ing

test-service         apiexample.centralus.cloudapp.azure.com   10.240.0.4   80, 443      15d

1 Ответ

0 голосов
/ 24 марта 2020

Я воспроизвел ваш сценарий в своей учетной записи GCP и не получил тот же результат, поэтому я публикую свои шаги по устранению неполадок каждого компонента, чтобы убедиться, что все они работают должным образом. В резюме, похоже, основная проблема заключается в том, как приложение обрабатывает paths или host.

Kubernetes: 1.15.3 (GKE) Nginx Вход: установлен после официальных документов

Исходя из вашего yaml, я удалил зонды readiness и liveness, а также среды envs для тестирования и изменил изображение на nginx (на порту 80):

apiVersion: v1
kind: Service
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: '"true"'
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: test-service
    app.kubernetes.io/instance: test-service
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: test-service
      app.kubernetes.io/instance: test-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: test-service
        app.kubernetes.io/instance: test-service
    spec:
      containers:
        - name: test-service
          image: nginx
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

После применения мы можем проверить, работают ли оба (развертывание и служба), как ожидалось, прежде чем применять входную скорость c.

Чтобы проверить это, мы можем использовать изображение curl для curl destination или dnsutil контейнер от официальных kubernetes docs . В этом случае я использовал curlimages/curl do test:

apiVersion: v1
kind: Pod
metadata:
  name: curl
  namespace: default
spec:
  containers:
  - name: curl
    image: curlimages/curl
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

При работающем контейнере curl мы можем сначала проверить, правильно ли работает контейнер нашего nginx образа и отвечает ли на запросы "curling" напрямую их IP.

Команда ниже создаст переменную с именем $pod из модуля с меткой app.kubernetes.io/name=test-service с IP-адресом модуля.

$ pod=$(kubectl get pods -ojsonpath='{.items[*].status.podIP}' -l app.kubernetes.io/name=test-service)

$ echo $pod
192.168.109.12

Использование curl pod create Ранее мы могли проверить, обрабатывает ли модуль запросы:

$ kubectl exec curl -- curl -Is $pod

HTTP/1.1 200 OK
Server: nginx/1.17.9
Date: Tue, 24 Mar 2020 09:08:21 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 03 Mar 2020 14:32:47 GMT
Connection: keep-alive
ETag: "5e5e6a8f-264"
Accept-Ranges: bytes

См. ответ HTTP / 1.1 200 OK , давайте продолжим тестировать службу:

$ kubectl exec curl -- curl -Is test-service
HTTP/1.1 200 OK
Server: nginx/1.17.9
Date: Tue, 24 Mar 2020 09:11:13 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 03 Mar 2020 14:32:47 GMT
Connection: keep-alive
ETag: "5e5e6a8f-264"
Accept-Ranges: bytes

То же самое здесь, HTTP / 1.1 200 OK для обслуживания.

Давайте go продолжим и развернем входной nginx без TLS, чтобы выполнить тест до и после:

Создание и применение сертификата:

$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=apiexample.centralus.cloudapp.azure.com/O=apiexample.centralus.cloudapp.azure.com"
...
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret/tls-secret created

Вход без TLS (я изменил на порт 80, чтобы соответствовать моему nginx образу):

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
    - host: "apiexample.centralus.cloudapp.azure.com"
      http:
        paths:
          - path: /testservice(/|$)(.*)
            backend:
              serviceName: test-service
              servicePort: 80

Тестирование с моего рабочего стола на inte rnet с использованием IP (не указан), предоставленного GCP:

$ curl -ILH "Host: apiexample.centralus.cloudapp.azure.com" http://34.77.xxx.xx/testservice
HTTP/1.1 200 OK
Server: nginx/1.17.8
Date: Tue, 24 Mar 2020 10:41:21 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Vary: Accept-Encoding
Last-Modified: Tue, 03 Mar 2020 14:32:47 GMT
ETag: "5e5e6a8f-264"
Accept-Ranges: bytes

Пока здесь все работает нормально. Теперь мы можем добавить TLS для входа в spe c и повторить попытку:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-service
  labels:
    app.kubernetes.io/name: test-service
    helm.sh/chart: test-service-0.1.0
    app.kubernetes.io/instance: test-service
    app.kubernetes.io/managed-by: Helm
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  tls:
  - hosts:
    - apiexample.centralus.cloudapp.azure.com
    secretName: tls-secret
  rules:
    - host: "apiexample.centralus.cloudapp.azure.com"
      http:
        paths:
          - path: /testservice(/|$)(.*)
            backend:
              serviceName: test-service
              servicePort: 80

Тестирование с использованием curl:

curl -ILH "Host: apiexample.centralus.cloudapp.azure.com" https://34.77.147.74/testservice -k
HTTP/2 200 
server: nginx/1.17.8
date: Tue, 24 Mar 2020 10:45:25 GMT
content-type: text/html
content-length: 612
vary: Accept-Encoding
last-modified: Tue, 03 Mar 2020 14:32:47 GMT
etag: "5e5e6a8f-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains

Хорошо, так что это работает с TLS, поэтому на основе из этого мы можем сделать вывод, что ваш yaml spe c работает, и, возможно, вы столкнулись с некоторой проблемой с path во входных определениях и вашем приложении:

Вы используете аннотацию nginx.ingress.kubernetes.io/rewrite-target: /$2 и путь /testservice(/|$)(.*)

Это означает, что любые символы, захваченные (.*), будут назначены заполнителю $2, который затем используется в качестве параметра в аннотации rewrite-target.

В зависимости от вашего входа path регулярное выражение:

  • apiexample.centralus.cloudapp.azure.com/testservice переписывает в apiexample.centralus.cloudapp.azure.com/

  • apiexample.centralus.cloudapp.azure.com/testservice/ переписывает в apiexample.centralus.cloudapp.azure.com/

  • apiexample.centralus.cloudapp.azure.com/testservice/tenant/api/v1/endpoint перезаписывает в apiexample.centralus.cloudapp.azure.com/tenant/api/v1/endpoint

При включении в nginx журналах модуля вы можете увидеть запрошенный URL после перезаписи:

2020/03/24 10:59:33 [error] 7#7: *186 open() "/usr/share/nginx/html/tenant/api/v1/endpoint" failed (2: No such file or directory), client: 10.20.1.61, server: localhost, request: "HEAD /tenant/api/v1/endpoint HTTP/1.1", host: "apiexample.centralus.cloudapp.azure.com"

Итак, с помощью этого теста я мог заключить, что ваше развертывание, обслуживание и вход работает и не содержит опечаток или форматирования. нг проблемы. Поэтому я советую дважды проверить приложение

  • Убедитесь, что ваше приложение правильно обрабатывает path;
  • Если ваши приложения выполняют некоторую проверку URL, убедитесь, что они могут обработать http и https;
  • Если вы включили CORS, настройте вход, как указано здесь .

Поскольку вы не разместили ни одного приложения в качестве примера для Воспроизведите, мои тесты были ограничены универсальным c приложением в качестве бэкэнда. Если вы могли бы предоставить более подробную информацию о бэкэнд-приложении или о каком-либо универсальном c приложении, которое производит то же поведение, пожалуйста, дайте мне знать и с удовольствием уточню мой ответ с более подробной информацией.

Ссылки:

https://kubernetes.github.io/ingress-nginx/deploy/

https://kubernetes.github.io/ingress-nginx/user-guide/ingress-path-matching/

https://kubernetes.github.io/ingress-nginx/user-guide/ingress-path-matching/

...