Add jwt-proxy functionality
[nonrtric.git] / service-exposure / rapps-jwt.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
7 //   Licensed under the Apache License, Version 2.0 (the "License");
8 //   you may not use this file except in compliance with the License.
9 //   You may obtain a copy of the License at
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
13 //   Unless required by applicable law or agreed to in writing, software
14 //   distributed under the License is distributed on an "AS IS" BASIS,
15 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 //   See the License for the specific language governing permissions and
17 //   limitations under the License.
18 //   ========================LICENSE_END===================================
19 //
20 package main
21
22 import (
23         "crypto/tls"
24         "crypto/x509"
25         "encoding/json"
26         "flag"
27         "fmt"
28         "io/ioutil"
29         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30         kubernetes "k8s.io/client-go/kubernetes"
31         "k8s.io/client-go/rest"
32         "net/http"
33         "net/url"
34         "rapps/utils/generatejwt"
35         "context"
36         "net"
37         "time"
38 )
39
40 type Jwttoken struct {
41         Access_token       string
42         Expires_in         int
43         Refresh_expires_in int
44         Refresh_token      string
45         Token_type         string
46         Not_before_policy  int
47         Session_state      string
48         Scope              string
49 }
50
51 var keycloakHost string
52 var keycloakPort string
53 var keycloakAlias string
54 var realmName string
55 var clientId string
56 var namespace string
57 var authenticator string
58 var healthy bool = true
59 var jwt Jwttoken
60
61 const (
62         scope                 = "email"
63         client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
64 )
65
66 func getToken(res http.ResponseWriter, req *http.Request) {
67         var resp = &http.Response{}
68         var err error
69         authenticator = req.Header.Get("authenticator")
70         clientId = req.Header.Get("client")
71         realmName = req.Header.Get("realm")
72         namespace = req.Header.Get("ns")
73         keycloakUrl := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName + "/protocol/openid-connect/token"
74         fmt.Printf("Making token request to %s\n", keycloakUrl)
75         res.Header().Set("Content-type", "application/json")
76         res.Header().Set("Authorization", "")
77
78         if authenticator == "client-jwt" {
79                 resp, err = getJwtToken(keycloakUrl, clientId)
80         } else if authenticator == "client-x509" {
81                 resp, err = getx509Token(keycloakUrl, clientId)
82         } else {
83                 resp, err = getSecretToken(keycloakUrl, clientId)
84         }
85
86         if err != nil {
87                 fmt.Println(err)
88                 res.WriteHeader(http.StatusInternalServerError)
89                 res.Write([]byte(err.Error()))
90                 panic("Something wrong with the credentials or url ")
91         }
92
93         defer resp.Body.Close()
94         body, err := ioutil.ReadAll(resp.Body)
95         json.Unmarshal([]byte(body), &jwt)
96         fmt.Printf("Token: %s\n", jwt.Access_token)
97
98         res.Header().Set("Authorization", "Bearer "+jwt.Access_token)
99         res.WriteHeader(http.StatusOK)
100         res.Write([]byte("Successfully retrieved JWT access token"))
101 }
102
103 func getJwtToken(keycloakUrl, clientId string) (*http.Response, error) {
104         var resp = &http.Response{}
105         var err error
106         client_assertion := getClientAssertion()
107
108         if jwt.Refresh_token != "" {
109                 resp, err = http.PostForm(keycloakUrl, url.Values{"client_assertion_type": {client_assertion_type},
110                         "client_assertion": {client_assertion}, "grant_type": {"refresh_token"},
111                         "refresh_token": {jwt.Refresh_token}, "client_id": {clientId}, "scope": {scope}})
112         } else {
113                 resp, err = http.PostForm(keycloakUrl, url.Values{"client_assertion_type": {client_assertion_type},
114                         "client_assertion": {client_assertion}, "grant_type": {"client_credentials"},
115                         "client_id": {clientId}, "scope": {scope}})
116         }
117
118         return resp, err
119 }
120
121 func getClientAssertion() string {
122         realm := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName
123         clientAssertion := generatejwt.CreateJWT("/certs/client.key", "", clientId, realm)
124         return clientAssertion
125 }
126
127 func getx509Token(keycloakUrl, clientId string) (*http.Response, error) {
128         var resp = &http.Response{}
129         var err error
130
131         client := getClient()
132         resp, err = client.PostForm(keycloakUrl, url.Values{"username": {""}, "password": {""}, "grant_type": {"password"}, "client_id": {clientId}, "scope": {scope}})
133
134         return resp, err
135 }
136
137 func getClient() *http.Client {
138         caCert, _ := ioutil.ReadFile("/certs/rootCA.crt")
139         caCertPool := x509.NewCertPool()
140         caCertPool.AppendCertsFromPEM(caCert)
141
142         cert, _ := tls.LoadX509KeyPair("/certs/client.crt", "/certs/client.key")
143
144         dialer := &net.Dialer{
145                 Timeout:   30 * time.Second,
146                 KeepAlive: 30 * time.Second,
147                 DualStack: true,
148         }
149
150         client := &http.Client{
151                 Transport: &http.Transport{
152                         DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
153                                 fmt.Println("address original =", addr)
154                                 if addr == keycloakAlias+":"+keycloakPort {
155                                         addr = keycloakHost + ":" + keycloakPort
156                                         fmt.Println("address modified =", addr)
157                                 }
158                                 return dialer.DialContext(ctx, network, addr)
159                         },
160                         TLSClientConfig: &tls.Config{
161                                 RootCAs:      caCertPool,
162                                 Certificates: []tls.Certificate{cert},
163                         },
164                 },
165         }
166         return client
167 }
168
169 func getSecretToken(keycloakUrl, clientId string) (*http.Response, error) {
170         var resp = &http.Response{}
171         var err error
172
173         secretName := clientId + "-secret"
174         clientSecret := getSecret(secretName)
175         resp, err = http.PostForm(keycloakUrl,
176                 url.Values{"client_secret": {clientSecret}, "grant_type": {"client_credentials"}, "client_id": {clientId}})
177
178         return resp, err
179 }
180
181 func getSecret(secretName string) string {
182         clientset := connectToK8s()
183         res, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
184         if err != nil {
185                 fmt.Println(err.Error())
186         }
187         return string(res.Data["client_secret"])
188 }
189
190 func connectToK8s() *kubernetes.Clientset {
191         config, err := rest.InClusterConfig()
192         if err != nil {
193                 fmt.Println("failed to create K8s config")
194         }
195
196         clientset, err := kubernetes.NewForConfig(config)
197         if err != nil {
198                 fmt.Println("Failed to create K8s clientset")
199         }
200
201         return clientset
202 }
203
204 func health(res http.ResponseWriter, req *http.Request) {
205         if healthy {
206                 res.WriteHeader(http.StatusOK)
207                 res.Write([]byte("healthy"))
208         } else {
209                 res.WriteHeader(http.StatusInternalServerError)
210                 res.Write([]byte("unhealthy"))
211         }
212 }
213
214 func main() {
215         flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host")
216         flag.StringVar(&keycloakPort, "keycloakPort", "80", "Keycloak Port")
217         flag.StringVar(&keycloakAlias, "keycloakAlias", "keycloak.oran.org", "Keycloak URL Alias")
218         flag.Parse()
219
220         healthHandler := http.HandlerFunc(health)
221         http.Handle("/health", healthHandler)
222         tokenHandler := http.HandlerFunc(getToken)
223         http.Handle("/token", tokenHandler)
224         http.ListenAndServe(":8888", nil)
225
226         ioutil.WriteFile("init.txt", []byte("Initialization done."), 0644)
227 }