Skip to content

Commit 96efaa5

Browse files
committed
iFrame Support
Signed-off-by: Daniel Valdivia <[email protected]>
1 parent bfedc14 commit 96efaa5

30 files changed

+427
-126
lines changed

operatorapi/configure_operator.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package operatorapi
2222
import (
2323
"crypto/tls"
2424
"net/http"
25+
"strings"
2526

2627
"github.com/minio/console/restapi"
2728
"github.com/unrolled/secure"
@@ -137,12 +138,25 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
137138
})
138139
}
139140

141+
// proxyMiddleware adds the proxy capability
142+
func proxyMiddleware(next http.Handler) http.Handler {
143+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
144+
if strings.HasPrefix(r.URL.Path, "/api/proxy") {
145+
serveProxy(w, r)
146+
} else {
147+
next.ServeHTTP(w, r)
148+
}
149+
})
150+
}
151+
140152
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
141153
// So this is a good place to plug in a panic handling middleware, logging and metrics.
142154
func setupGlobalMiddleware(handler http.Handler) http.Handler {
143155
// handle cookie or authorization header for session
144156
next := AuthenticationMiddleware(handler)
145157
// serve static files
158+
next = proxyMiddleware(next)
159+
// serve static files
146160
next = restapi.FileServerMiddleware(next)
147161
// Secure middleware, this middleware wrap all the previous handlers and add
148162
// HTTP security headers

operatorapi/proxy.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2021 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package operatorapi
18+
19+
import (
20+
"bytes"
21+
"crypto/sha1"
22+
"crypto/tls"
23+
"encoding/json"
24+
"fmt"
25+
"io"
26+
"log"
27+
"net/http"
28+
"net/http/cookiejar"
29+
url2 "net/url"
30+
"strings"
31+
32+
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
33+
34+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+
36+
"github.com/minio/console/cluster"
37+
38+
"github.com/minio/console/pkg/auth"
39+
)
40+
41+
func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
42+
urlParts := strings.Split(req.URL.Path, "/")
43+
44+
if len(urlParts) < 5 {
45+
log.Println(len(urlParts))
46+
return
47+
}
48+
namespace := urlParts[3]
49+
tenantName := urlParts[4]
50+
51+
// validate the tenantName
52+
53+
token, err := auth.GetTokenFromRequest(req)
54+
if err != nil {
55+
log.Println(err)
56+
responseWriter.WriteHeader(401)
57+
return
58+
}
59+
claims, err := auth.SessionTokenAuthenticate(token)
60+
if err != nil {
61+
log.Printf("Unable to validate the session token %s: %v\n", token, err)
62+
responseWriter.WriteHeader(401)
63+
64+
return
65+
}
66+
67+
//STSSessionToken := currToken.Value
68+
STSSessionToken := claims.STSSessionToken
69+
70+
opClientClientSet, err := cluster.OperatorClient(STSSessionToken)
71+
if err != nil {
72+
log.Println(err)
73+
responseWriter.WriteHeader(404)
74+
return
75+
}
76+
opClient := operatorClient{
77+
client: opClientClientSet,
78+
}
79+
tenant, err := opClient.TenantGet(req.Context(), namespace, tenantName, metav1.GetOptions{})
80+
if err != nil {
81+
log.Println(err)
82+
responseWriter.WriteHeader(502)
83+
return
84+
}
85+
86+
nsTenant := fmt.Sprintf("%s/%s", tenant.Namespace, tenant.Name)
87+
88+
tenantSchema := "http"
89+
tenantPort := ":9090"
90+
if tenant.AutoCert() || tenant.ConsoleExternalCert() {
91+
tenantSchema = "https"
92+
tenantPort = ":9443"
93+
}
94+
tenantURL := fmt.Sprintf("%s://%s.%s.svc.%s%s", tenantSchema, tenant.ConsoleCIServiceName(), tenant.Namespace, v2.GetClusterDomain(), tenantPort)
95+
// for development
96+
//tenantURL = "http://localhost:9091"
97+
//tenantURL = "https://localhost:9443"
98+
99+
h := sha1.New()
100+
h.Write([]byte(nsTenant))
101+
log.Printf("Proxying request for %s/%s", namespace, tenantName)
102+
tenantCookieName := fmt.Sprintf("token-%x", string(h.Sum(nil)))
103+
tenantCookie, err := req.Cookie(tenantCookieName)
104+
if err != nil {
105+
// login to tenantName
106+
loginURL := fmt.Sprintf("%s/api/v1/login", tenantURL)
107+
108+
// get the tenant credentials
109+
clientSet, err := cluster.K8sClient(STSSessionToken)
110+
if err != nil {
111+
log.Println(err)
112+
responseWriter.WriteHeader(500)
113+
return
114+
}
115+
116+
currentSecret, err := clientSet.CoreV1().Secrets(namespace).Get(req.Context(), tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
117+
if err != nil {
118+
log.Println(err)
119+
responseWriter.WriteHeader(500)
120+
return
121+
}
122+
123+
data := map[string]string{
124+
"accessKey": string(currentSecret.Data["accesskey"]),
125+
"secretKey": string(currentSecret.Data["secretkey"]),
126+
}
127+
payload, _ := json.Marshal(data)
128+
129+
loginReq, err := http.NewRequest(http.MethodPost, loginURL, bytes.NewReader(payload))
130+
if err != nil {
131+
log.Println(err)
132+
responseWriter.WriteHeader(500)
133+
return
134+
}
135+
loginReq.Header.Add("Content-Type", "application/json")
136+
137+
// FIXME: in the future we should use restapi.GetConsoleSTSClient()
138+
tr := &http.Transport{
139+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
140+
}
141+
client := &http.Client{Transport: tr}
142+
143+
loginResp, err := client.Do(loginReq)
144+
if err != nil {
145+
log.Println(err)
146+
responseWriter.WriteHeader(500)
147+
return
148+
}
149+
150+
for _, c := range loginResp.Cookies() {
151+
if c.Name == "token" {
152+
tenantCookie = c
153+
c := &http.Cookie{
154+
Name: tenantCookieName,
155+
Value: c.Value,
156+
Path: c.Path,
157+
Expires: c.Expires,
158+
HttpOnly: c.HttpOnly,
159+
}
160+
161+
http.SetCookie(responseWriter, c)
162+
break
163+
}
164+
}
165+
defer loginResp.Body.Close()
166+
}
167+
168+
targetURL, err := url2.Parse(tenantURL)
169+
if err != nil {
170+
log.Println(err)
171+
responseWriter.WriteHeader(500)
172+
return
173+
}
174+
targetURL.Path = strings.Replace(req.URL.Path, fmt.Sprintf("/api/proxy/%s/%s", tenant.Namespace, tenant.Name), "", -1)
175+
176+
proxiedCookie := &http.Cookie{
177+
Name: "token",
178+
Value: tenantCookie.Value,
179+
Path: tenantCookie.Path,
180+
Expires: tenantCookie.Expires,
181+
HttpOnly: tenantCookie.HttpOnly,
182+
}
183+
184+
proxyCookieJar, _ := cookiejar.New(nil)
185+
proxyCookieJar.SetCookies(targetURL, []*http.Cookie{proxiedCookie})
186+
187+
tr := &http.Transport{
188+
// FIXME: use restapi.GetConsoleSTSClient()
189+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
190+
}
191+
client := &http.Client{Transport: tr,
192+
Jar: proxyCookieJar,
193+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
194+
return http.ErrUseLastResponse
195+
}}
196+
197+
proxRequest, err := http.NewRequest(req.Method, targetURL.String(), req.Body)
198+
if err != nil {
199+
log.Println(err)
200+
responseWriter.WriteHeader(500)
201+
return
202+
}
203+
204+
for _, v := range req.Header.Values("Content-Type") {
205+
proxRequest.Header.Add("Content-Type", v)
206+
}
207+
208+
resp, err := client.Do(proxRequest)
209+
if err != nil {
210+
log.Println(err)
211+
responseWriter.WriteHeader(500)
212+
return
213+
}
214+
215+
for hk, hv := range resp.Header {
216+
if hk != "X-Frame-Options" {
217+
for _, v := range hv {
218+
responseWriter.Header().Add(hk, v)
219+
}
220+
}
221+
}
222+
// Allow iframes
223+
responseWriter.Header().Set("X-Frame-Options", "SAMEORIGIN")
224+
responseWriter.Header().Set("X-XSS-Protection", "1")
225+
226+
io.Copy(responseWriter, resp.Body)
227+
228+
}

pkg/acl/endpoints.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var (
2929
iamPolicies = "/policies"
3030
policiesDetail = "/policies/:policyName"
3131
dashboard = "/dashboard"
32+
metrics = "/metrics"
3233
profiling = "/profiling"
3334
buckets = "/buckets"
3435
bucketsDetail = "/buckets/:bucketName"
@@ -293,6 +294,7 @@ var endpointRules = map[string]ConfigurationActionSet{
293294
iamPolicies: iamPoliciesActionSet,
294295
policiesDetail: iamPoliciesActionSet,
295296
dashboard: dashboardActionSet,
297+
metrics: dashboardActionSet,
296298
profiling: profilingActionSet,
297299
buckets: bucketsActionSet,
298300
bucketsDetail: bucketsActionSet,

pkg/acl/endpoints_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
5050
args: args{
5151
[]string{"admin:ServerInfo"},
5252
},
53-
want: 7,
53+
want: 8,
5454
},
5555
{
5656
name: "policies endpoint",
@@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
7272
"admin:*",
7373
},
7474
},
75-
want: 21,
75+
want: 22,
7676
},
7777
{
7878
name: "all s3 endpoints",
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
9191
"s3:*",
9292
},
9393
},
94-
want: 30,
94+
want: 31,
9595
},
9696
{
9797
name: "Console User - default endpoints",
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"files": {
33
"main.css": "/static/css/main.8cfac526.chunk.css",
4-
"main.js": "/static/js/main.19a8b820.chunk.js",
5-
"main.js.map": "/static/js/main.19a8b820.chunk.js.map",
4+
"main.js": "/static/js/main.aed25c81.chunk.js",
5+
"main.js.map": "/static/js/main.aed25c81.chunk.js.map",
66
"runtime-main.js": "/static/js/runtime-main.43a31377.js",
77
"runtime-main.js.map": "/static/js/runtime-main.43a31377.js.map",
8-
"static/css/2.20c81d8d.chunk.css": "/static/css/2.20c81d8d.chunk.css",
9-
"static/js/2.c66fcba0.chunk.js": "/static/js/2.c66fcba0.chunk.js",
10-
"static/js/2.c66fcba0.chunk.js.map": "/static/js/2.c66fcba0.chunk.js.map",
8+
"static/css/2.c5a51b70.chunk.css": "/static/css/2.c5a51b70.chunk.css",
9+
"static/js/2.dd405112.chunk.js": "/static/js/2.dd405112.chunk.js",
10+
"static/js/2.dd405112.chunk.js.map": "/static/js/2.dd405112.chunk.js.map",
1111
"index.html": "/index.html",
12-
"static/css/2.20c81d8d.chunk.css.map": "/static/css/2.20c81d8d.chunk.css.map",
12+
"static/css/2.c5a51b70.chunk.css.map": "/static/css/2.c5a51b70.chunk.css.map",
1313
"static/css/main.8cfac526.chunk.css.map": "/static/css/main.8cfac526.chunk.css.map",
14-
"static/js/2.c66fcba0.chunk.js.LICENSE.txt": "/static/js/2.c66fcba0.chunk.js.LICENSE.txt",
14+
"static/js/2.dd405112.chunk.js.LICENSE.txt": "/static/js/2.dd405112.chunk.js.LICENSE.txt",
1515
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
1616
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
1717
},
1818
"entrypoints": [
1919
"static/js/runtime-main.43a31377.js",
20-
"static/css/2.20c81d8d.chunk.css",
21-
"static/js/2.c66fcba0.chunk.js",
20+
"static/css/2.c5a51b70.chunk.css",
21+
"static/js/2.dd405112.chunk.js",
2222
"static/css/main.8cfac526.chunk.css",
23-
"static/js/main.19a8b820.chunk.js"
23+
"static/js/main.aed25c81.chunk.js"
2424
]
2525
}

portal-ui/build/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.20c81d8d.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.c66fcba0.chunk.js"></script><script src="/static/js/main.19a8b820.chunk.js"></script></body></html>
1+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.c5a51b70.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.dd405112.chunk.js"></script><script src="/static/js/main.aed25c81.chunk.js"></script></body></html>

0 commit comments

Comments
 (0)