First version of service exposure
[nonrtric.git] / service-exposure / rapps-istio-mgr.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
21 package main
22
23 import (
24         "bytes"
25         "context"
26         "fmt"
27         netv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
28         secv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1"
29         versioned "istio.io/client-go/pkg/clientset/versioned"
30         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31         k8Yaml "k8s.io/apimachinery/pkg/util/yaml"
32         "k8s.io/client-go/rest"
33         clientcmd "k8s.io/client-go/tools/clientcmd"
34         "log"
35         "net/http"
36         "os"
37         "path/filepath"
38         "strings"
39 )
40
41 const (
42         NAMESPACE = "istio-nonrtric"
43 )
44
45 var gatewayManifest = `
46 apiVersion: networking.istio.io/v1beta1
47 kind: Gateway
48 metadata:
49   name: nonrtric-istio-RAPP-NAME-gateway
50   namespace: RAPP-NS 
51 spec:
52   selector:
53     istio: ingressgateway # use Istio gateway implementation
54   servers:
55   - port:
56       number: 80
57       name: http
58       protocol: HTTP
59     hosts:
60     - "*"
61 `
62
63 var virtualServiceManifest = `
64 apiVersion: networking.istio.io/v1beta1
65 kind: VirtualService
66 metadata:
67   name: nonrtric-istio-RAPP-NAME-vs
68   namespace: RAPP-NS 
69 spec:
70   hosts:
71   - "*"
72   gateways:
73   - nonrtric-istio-RAPP-NAME-gateway
74   http:
75   - name: "RAPP-NAME-routes"
76     match:
77     - uri:
78         prefix: "/RAPP-NAME"
79     route:
80     - destination:
81         port:
82           number: 80
83         host: RAPP-NAME.RAPP-NS.svc.cluster.local
84 `
85
86 var requestAuthenticationManifest = `
87 apiVersion: security.istio.io/v1beta1
88 kind: RequestAuthentication
89 metadata:
90   name: "jwt-RAPP-NAME"
91   namespace: RAPP-NS 
92 spec:
93   selector:
94     matchLabels:
95       app.kubernetes.io/instance: RAPP-NAME
96   jwtRules:
97   - issuer: "http://192.168.49.2:31560/auth/realms/REALM-NAME"
98     jwksUri: "http://192.168.49.2:31560/auth/realms/REALM-NAME/protocol/openid-connect/certs"
99   - issuer: "http://keycloak.default:8080/auth/realms/REALM-NAME"
100     jwksUri: "http://keycloak.default:8080/auth/realms/REALM-NAME/protocol/openid-connect/certs"
101   - issuer: "https://192.168.49.2:31561/auth/realms/REALM-NAME"
102     jwksUri: "https://192.168.49.2:31561/auth/realms/REALM-NAME/protocol/openid-connect/certs"
103   - issuer: "https://keycloak.default:8443/auth/realms/REALM-NAME"
104     jwksUri: "https://keycloak.default:8443/auth/realms/REALM-NAME/protocol/openid-connect/certs"
105   - issuer: "https://keycloak.est.tech:443/auth/realms/REALM-NAME"
106     jwksUri: "https://keycloak.default:8443/auth/realms/REALM-NAME/protocol/openid-connect/certs"
107   - issuer: "http://istio-ingressgateway.istio-system:80/auth/realms/REALM-NAME"
108     jwksUri: "http://keycloak.default:8080/auth/realms/REALM-NAME/protocol/openid-connect/certs"
109 `
110
111 var authorizationPolicyManifest = `
112 apiVersion: "security.istio.io/v1beta1"
113 kind: "AuthorizationPolicy"
114 metadata:
115   name: "RAPP-NAME-policy"
116   namespace: RAPP-NS 
117 spec:
118   selector:
119     matchLabels:
120       app.kubernetes.io/instance: RAPP-NAME
121   action: ALLOW
122   rules:
123   - from:
124     - source:
125         requestPrincipals: ["http://192.168.49.2:31560/auth/realms/REALM-NAME/", "http://keycloak.default:8080/auth/realms/REALM-NAME/", "https://192.168.49.2:31561/auth/realms/REALM-NAME/", "https://keycloak.default:8443/auth/realms/REALM-NAME/", "https://keycloak.est.tech:443/auth/realms/REALM-NAME/", "http://istio-ingressgateway.istio-system:80/auth/realms/REALM-NAME/"]
126   - to:
127     - operation:
128         methods: ["METHOD-NAME"]
129         paths: ["/RAPP-NAME"]
130     when:
131     - key: request.auth.claims[clientRole]
132       values: ["ROLE-NAME"]
133 `
134
135 func connectToK8s() *versioned.Clientset {
136         config, err := rest.InClusterConfig()
137         if err != nil {
138                 // fallback to kubeconfig
139                 home, exists := os.LookupEnv("HOME")
140                 if !exists {
141                         home = "/root"
142                 }
143
144                 kubeconfig := filepath.Join(home, ".kube", "config")
145                 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
146                         kubeconfig = envvar
147                 }
148                 config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
149                 if err != nil {
150                         log.Fatalln("failed to create K8s config")
151                 }
152         }
153
154         ic, err := versioned.NewForConfig(config)
155         if err != nil {
156                 log.Fatalf("Failed to create istio client: %s", err)
157         }
158
159         return ic
160 }
161
162 func createGateway(clientset *versioned.Clientset, appName string) (string, error) {
163         gtClient := clientset.NetworkingV1beta1().Gateways(NAMESPACE)
164         manifest := strings.Replace(gatewayManifest, "RAPP-NAME", appName, -1)
165         manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
166
167         gt := &netv1beta1.Gateway{}
168         dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
169
170         if err := dec.Decode(&gt); err != nil {
171                 return "", err
172         }
173
174         result, err := gtClient.Create(context.TODO(), gt, metav1.CreateOptions{})
175
176         if err != nil {
177                 return "", err
178         }
179
180         fmt.Printf("Create Gateway %s \n", result.GetName())
181         return result.GetName(), nil
182 }
183
184 func createVirtualService(clientset *versioned.Clientset, appName string) (string, error) {
185         vsClient := clientset.NetworkingV1beta1().VirtualServices(NAMESPACE)
186         manifest := strings.Replace(virtualServiceManifest, "RAPP-NAME", appName, -1)
187         manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
188
189         vs := &netv1beta1.VirtualService{}
190         dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
191
192         if err := dec.Decode(&vs); err != nil {
193                 return "", err
194         }
195
196         result, err := vsClient.Create(context.TODO(), vs, metav1.CreateOptions{})
197
198         if err != nil {
199                 return "", err
200         }
201
202         fmt.Printf("Create Virtual Service %s \n", result.GetName())
203         return result.GetName(), nil
204 }
205
206 func createRequestAuthentication(clientset *versioned.Clientset, appName, realmName string) (string, error) {
207         raClient := clientset.SecurityV1beta1().RequestAuthentications(NAMESPACE)
208         manifest := strings.Replace(requestAuthenticationManifest, "RAPP-NAME", appName, -1)
209         manifest = strings.Replace(manifest, "REALM-NAME", realmName, -1)
210         manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
211
212         ra := &secv1beta1.RequestAuthentication{}
213         dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
214
215         if err := dec.Decode(&ra); err != nil {
216                 return "", err
217         }
218
219         result, err := raClient.Create(context.TODO(), ra, metav1.CreateOptions{})
220
221         if err != nil {
222                 return "", err
223         }
224
225         fmt.Printf("Create Request Authentication %s \n", result.GetName())
226         return result.GetName(), nil
227 }
228
229 func createAuthorizationPolicy(clientset *versioned.Clientset, appName, realmName, roleName, methodName string) (string, error) {
230         apClient := clientset.SecurityV1beta1().AuthorizationPolicies(NAMESPACE)
231         manifest := strings.Replace(authorizationPolicyManifest, "RAPP-NAME", appName, -1)
232         manifest = strings.Replace(manifest, "REALM-NAME", realmName, -1)
233         manifest = strings.Replace(manifest, "ROLE-NAME", roleName, -1)
234         manifest = strings.Replace(manifest, "METHOD-NAME", methodName, -1)
235         manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
236
237         ap := &secv1beta1.AuthorizationPolicy{}
238         dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
239
240         if err := dec.Decode(&ap); err != nil {
241                 return "", err
242         }
243
244         result, err := apClient.Create(context.TODO(), ap, metav1.CreateOptions{})
245
246         if err != nil {
247                 return "", err
248         }
249
250         fmt.Printf("Create Authorization Policy %s \n", result.GetName())
251         return result.GetName(), nil
252 }
253
254 func removeGateway(clientset *versioned.Clientset, appName string) {
255         gtClient := clientset.NetworkingV1beta1().Gateways(NAMESPACE)
256         err := gtClient.Delete(context.TODO(), "nonrtric-istio-"+appName+"-gateway", metav1.DeleteOptions{})
257         if err != nil {
258                 fmt.Println(err)
259         } else {
260                 fmt.Println("Deleted Gateway nonrtric-istio-" + appName + "-gateway")
261         }
262 }
263
264 func removeVirtualService(clientset *versioned.Clientset, appName string) {
265         vsClient := clientset.NetworkingV1beta1().VirtualServices(NAMESPACE)
266         err := vsClient.Delete(context.TODO(), "nonrtric-istio-"+appName+"-vs", metav1.DeleteOptions{})
267         if err != nil {
268                 fmt.Println(err)
269         } else {
270                 fmt.Println("Deleted VirtualServices nonrtric-istio-" + appName + "-vs")
271         }
272 }
273
274 func removeRequestAuthentication(clientset *versioned.Clientset, appName string) {
275         raClient := clientset.SecurityV1beta1().RequestAuthentications(NAMESPACE)
276         err := raClient.Delete(context.TODO(), "jwt-"+appName, metav1.DeleteOptions{})
277         if err != nil {
278                 fmt.Println(err)
279         } else {
280                 fmt.Println("Deleted RequestAuthentication jwt-" + appName)
281         }
282 }
283
284 func removeAuthorizationPolicy(clientset *versioned.Clientset, appName string) {
285         apClient := clientset.SecurityV1beta1().AuthorizationPolicies(NAMESPACE)
286         err := apClient.Delete(context.TODO(), appName+"-policy", metav1.DeleteOptions{})
287         if err != nil {
288                 fmt.Println(err)
289         } else {
290                 fmt.Println("Deleted AuthorizationPolicy " + appName + "-policy")
291         }
292 }
293
294 func createIstioPolicy(res http.ResponseWriter, req *http.Request) {
295         query := req.URL.Query()
296         realmName := query.Get("realm")
297         appName := query.Get("name")
298         roleName := query.Get("role")
299         methodName := query.Get("method")
300         var msg string
301         clientset := connectToK8s()
302         _, err := createGateway(clientset, appName)
303         if err != nil {
304                 msg = err.Error()
305                 fmt.Println(err.Error())
306         } else {
307                 _, err := createVirtualService(clientset, appName)
308                 if err != nil {
309                         msg = err.Error()
310                         fmt.Println(err.Error())
311                 } else {
312                         _, err := createRequestAuthentication(clientset, appName, realmName)
313                         if err != nil {
314                                 msg = err.Error()
315                                 fmt.Println(err.Error())
316                         } else {
317                                 _, err := createAuthorizationPolicy(clientset, appName, realmName, roleName, methodName)
318                                 if err != nil {
319                                         msg = err.Error()
320                                         fmt.Println(err.Error())
321                                 } else {
322                                         msg = "Istio rapp security setup successfully"
323                                 }
324                         }
325                 }
326         }
327
328         // create response binary data
329         data := []byte(msg) // slice of bytes
330         // write `data` to response
331         res.Write(data)
332 }
333
334 func removeIstioPolicy(res http.ResponseWriter, req *http.Request) {
335         query := req.URL.Query()
336         appName := query.Get("name")
337         clientset := connectToK8s()
338         removeAuthorizationPolicy(clientset, appName)
339         removeRequestAuthentication(clientset, appName)
340         removeVirtualService(clientset, appName)
341         removeGateway(clientset, appName)
342 }
343
344 func main() {
345         createIstioHandler := http.HandlerFunc(createIstioPolicy)
346         http.Handle("/create", createIstioHandler)
347         removeIstioHandler := http.HandlerFunc(removeIstioPolicy)
348         http.Handle("/remove", removeIstioHandler)
349         http.ListenAndServe(":9000", nil)
350 }