레이블이 authorization인 게시물을 표시합니다. 모든 게시물 표시
레이블이 authorization인 게시물을 표시합니다. 모든 게시물 표시

js fetch() with credntial 옵션 사용시 CORS 에러 방지 서버 설정

# javascript(js) fetch 함수 요청시 CORS(Cross-Origin Resource Sharing) 에러 방지하기
# js fetch() 로 ysoftman1 에서 ysoftman2 로 요청하는 상황으로 다음과 같다.
# 크롬 개발자 도구 console 에서 실행
fetch("http://ysoftman2/lemon", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "authorization": "Bearer aaabbbccc123456789",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://ysoftman1/apple",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
}).then(response => response.json());

# 이때 요청 흐름은 js fetch() --> browser --> server 이 된다.
# browser 는 OPTIONS 메소드로 서버에게 접근 가능한지 물어본다.(preflight 과정)
# 이때 서버는 응답 헤더에 다음 값들을 설정할 수 있고
Access-Control-Allow-Origin: 접근 가능한 호스트
Access-Control-Allow-Methods: 접근 가능한 메소드
Access-Control-Allow-Headers: 접근 가능한 헤더

# browser 는 이 허용된 값들 내에서 서버에 실제 요청을 하게 된다.

# 서버가 와일드카드(Access-Control-Allow-Origin: *)를 사용하더라도 
# 클라가 credential (ajax, xmlhttp 로 다른도메인에 쿠키를 설정할때 사용하는 옵션)를 사용하면 CORS 정책으로 블럭된다.
# 다음과 같이 exact 한 호스명과 vary (브라우저가 캐시 사용시 어떤 헤더를 보고 구분해야 하는지 알려준다) 헤더에 origin 을 설정해야 한다.
Access-Control-Allow-Origin: https://developer.mozilla.org
Vary: Origin

# 서버쪽 설정 방법들
# 방법1 - k8s ingress 사용시, enable-cors true 설정(디폴트 false)
# enable-cors 를 사용하면 cors 관련 응답헤더들(Access-control-xxx)이 자동으로 추가된다.
# 그리고 Access-Control-Allow-Origin 가 * 로 고정된다.
# configuration-snippet 의 Access-Control-xxx 헤더가 응답에 중복되어
# cors 가 동작하지 않으니 같이 사용하지 않는다.
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-headers: "x-test-header"
    
# 방법2 - k8s ingress 사용시, configuration-snippet 로 설정
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # configuration-snippet 현재 location 의 설정에 추가된다.
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        add_header 'Access-Control-Max-Age' "1728000";
        add_header 'Content-Type' "text/plain charset=UTF-8";
        add_header 'Content-Length' "0";
        add_header 'Vary' "Origin";
        return 204;
      }
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Credentials' "true";
      add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
      add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
      add_header 'Vary' "Origin";

# 방법3 - k8s ingress 사용시, 특정 도메인에만 cors 허용할때 변수를 설정해 구분
metadata
  annotations
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($http_origin ~* "^https?:\/\/(.*\.)?((ysoftman\-lemon\.com)|(ysoftman\-apple\.com))$") {
        set $cors = "cors";
      }
      if ($request_method = 'OPTIONS') {
        set $cors = "cors_options";
      }
      if ($cors = "cors_options") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 200;
      }
      if ($cors = "cors") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 204;
      }

# 방법4 - 서버에 직접 설정하는 경우
# 아래는 go chi 핸들러 사용하는 예시
func CORSHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    // CORS(Cross-Origin Resource Sharing) 에러 방지 헤더 추가
    if r.Method == http.MethodOptions {
      // w.Header().Set("Access-Control-Allow-Origin", "*")
      // js fetch() credentials 옵션 사용시 와일드카드(*) 대신 요청 origin 으로 설정해야 한다.
      w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
      w.Header().Set("Access-Control-Allow-Credentials", "true")
      w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
      w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
      // options (preflight) 일때는 CORS 에러 방지 헤더 추가하여 바로 응답을 줘 클라가 다시 요청을 시도할 수 있도록 한다.
      return
    }
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    next.ServeHTTP(w, r)
  }
  return http.HandlerFunc(fn)
}

# 주의사항
# 만약 k8s ingress 과 서버내 양쪽에서 모두 access-controll-allow 헤더들을 설정하면 중복 설정되고 CORS 정책에 위배돼 에러가 발생한다.
The 'Access-Control-Allow-Origin' header contains multiple values 'https://....', 
but only one is allowed. 
Have the server send the header with a valid value, or, 
if an opaque response serves your needs, 
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

# 그래서 한쪽에서만 설정해줘야 한다.
# 참고로 curl 로 cors 확인(access-control-xxx 헤더)을 위해선 다음 옵션을 사용해야 한다.
curl -X GET "https://ysoftman.lemon.com/aaa/bbb?ccc=lemon" \
-H "accept: application/json" \
--header 'Origin: http://ysoftman.lemon.com' \
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET'

k8s service account 생성

# 외부 drone ci 등에 신규 k8s 클러스터에 접근이 필요한 경우
# service account 와 clusterrolebinding(Role-based access control (RBAC)) 리소스를 생성하고
# service account 의 token을 사용하면 된다.

# 다음과 같이 service account 명세를 작성한다.
cat << zzz > service_account_for_droneci.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: drone-ci
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: drone-ci-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: drone-ci
  namespace: default
zzz

# 이제 적용하면 serviceAccount 가 secret 이 생성된다.
kubectl apply -f service_account_for_droneci.yaml

# drone-ci-token-xxx 를 파악해서 ~/.kube/config 에서 사용하면 된다.
kubectl get secret $(kubectl get secret -n default | grep drone | awk '{print $1}') -n default -o jsonpath="{.data.token}"

# 인증서 확인
kubectl get secrets

# 참고로 A 네임스페이스의 인증서 B 네임스페이스에 적용하기
kubectl get secret -n A ysoftman-test-cert -o yaml > cert.yaml
# cert.yaml 의 namespace=B 로 수정해서 적용한다.
kubectl apply -f cert.yaml

3A

3A 는 Authentication(인증), Authorization(인가), Account(계정,과금) 을 말하며, Diameter Protocol (RFC 6733) 에서 구현하고 있는 내용이다.

참고
https://ko.wikipedia.org/wiki/RFC
https://ko.wikipedia.org/wiki/%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%AF%B8%ED%84%B0_(%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C)