Update keycloak version
[nonrtric.git] / service-exposure / rapps-jwt.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022-2023: 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 + "/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                 keycloakPort = "443"
82                 keycloakUrl := "https://" + keycloakAlias + ":" + keycloakPort + "/realms/" + realmName + "/protocol/openid-connect/token"
83                 resp, err = getx509Token(keycloakUrl, clientId)
84         } else {
85                 resp, err = getSecretToken(keycloakUrl, clientId)
86         }
87
88         if err != nil {
89                 fmt.Println(err)
90                 res.WriteHeader(http.StatusInternalServerError)
91                 res.Write([]byte(err.Error()))
92                 panic("Something wrong with the credentials or url ")
93         }
94
95         defer resp.Body.Close()
96         body, err := ioutil.ReadAll(resp.Body)
97         json.Unmarshal([]byte(body), &jwt)
98         fmt.Printf("Token: %s\n", jwt.Access_token)
99
100         res.Header().Set("Authorization", "Bearer "+jwt.Access_token)
101         res.WriteHeader(http.StatusOK)
102         res.Write([]byte("Successfully retrieved JWT access token"))
103 }
104
105 func getJwtToken(keycloakUrl, clientId string) (*http.Response, error) {
106         var resp = &http.Response{}
107         var err error
108         client_assertion := getClientAssertion()
109
110         if jwt.Refresh_token != "" {
111                 resp, err = http.PostForm(keycloakUrl, url.Values{"client_assertion_type": {client_assertion_type},
112                         "client_assertion": {client_assertion}, "grant_type": {"refresh_token"},
113                         "refresh_token": {jwt.Refresh_token}, "client_id": {clientId}, "scope": {scope}})
114         } else {
115                 resp, err = http.PostForm(keycloakUrl, url.Values{"client_assertion_type": {client_assertion_type},
116                         "client_assertion": {client_assertion}, "grant_type": {"client_credentials"},
117                         "client_id": {clientId}, "scope": {scope}})
118         }
119
120         return resp, err
121 }
122
123 func getClientAssertion() string {
124         //aud := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName
125         //aud := "http://keycloak/auth/realms/" + realmName
126         aud := "https://keycloak:8443/realms/" + realmName
127         clientAssertion := generatejwt.CreateJWT("/certs/client.key", "", clientId, aud)
128         return clientAssertion
129 }
130
131 func getx509Token(keycloakUrl, clientId string) (*http.Response, error) {
132         var resp = &http.Response{}
133         var err error
134
135         client := getClient()
136         resp, err = client.PostForm(keycloakUrl, url.Values{"username": {""}, "password": {""}, "grant_type": {"password"}, "client_id": {clientId}, "scope": {scope}})
137
138         return resp, err
139 }
140
141 func getClient() *http.Client {
142         caCert, _ := ioutil.ReadFile("/certs/rootCA.crt")
143         caCertPool := x509.NewCertPool()
144         caCertPool.AppendCertsFromPEM(caCert)
145
146         cert, _ := tls.LoadX509KeyPair("/certs/client.crt", "/certs/client.key")
147
148         dialer := &net.Dialer{
149                 Timeout:   30 * time.Second,
150                 KeepAlive: 30 * time.Second,
151                 DualStack: true,
152         }
153
154         client := &http.Client{
155                 Transport: &http.Transport{
156                         DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
157                                 fmt.Println("address original =", addr)
158                                 if addr == keycloakAlias+":"+keycloakPort {
159                                         addr = keycloakHost + ":" + keycloakPort
160                                         fmt.Println("address modified =", addr)
161                                 }
162                                 return dialer.DialContext(ctx, network, addr)
163                         },
164                         TLSClientConfig: &tls.Config{
165                                 RootCAs:      caCertPool,
166                                 Certificates: []tls.Certificate{cert},
167                         },
168                 },
169         }
170         return client
171 }
172
173 func getSecretToken(keycloakUrl, clientId string) (*http.Response, error) {
174         var resp = &http.Response{}
175         var err error
176
177         secretName := clientId + "-secret"
178         clientSecret := getSecret(secretName)
179         resp, err = http.PostForm(keycloakUrl,
180                 url.Values{"client_secret": {clientSecret}, "grant_type": {"client_credentials"}, "client_id": {clientId}})
181
182         return resp, err
183 }
184
185 func getSecret(secretName string) string {
186         clientset := connectToK8s()
187         res, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
188         if err != nil {
189                 fmt.Println(err.Error())
190         }
191         return string(res.Data["client_secret"])
192 }
193
194 func connectToK8s() *kubernetes.Clientset {
195         config, err := rest.InClusterConfig()
196         if err != nil {
197                 fmt.Println("failed to create K8s config")
198         }
199
200         clientset, err := kubernetes.NewForConfig(config)
201         if err != nil {
202                 fmt.Println("Failed to create K8s clientset")
203         }
204
205         return clientset
206 }
207
208 func health(res http.ResponseWriter, req *http.Request) {
209         if healthy {
210                 res.WriteHeader(http.StatusOK)
211                 res.Write([]byte("healthy"))
212         } else {
213                 res.WriteHeader(http.StatusInternalServerError)
214                 res.Write([]byte("unhealthy"))
215         }
216 }
217
218 func main() {
219         flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host")
220         flag.StringVar(&keycloakPort, "keycloakPort", "80", "Keycloak Port")
221         flag.StringVar(&keycloakAlias, "keycloakAlias", "keycloak.est.tech", "Keycloak URL Alias")
222         flag.Parse()
223
224         healthHandler := http.HandlerFunc(health)
225         http.Handle("/health", healthHandler)
226         tokenHandler := http.HandlerFunc(getToken)
227         http.Handle("/token", tokenHandler)
228         http.ListenAndServe(":8888", nil)
229
230         ioutil.WriteFile("init.txt", []byte("Initialization done."), 0644)
231 }