From 5e65cd77107f72f1bdc775b707aeffb6a77769ec Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Sun, 31 Mar 2019 17:43:31 +0100 Subject: [PATCH 01/34] Minor updates --- .../helm-release-service-card.component.html | 10 +++ .../helm-release-service-card.component.ts | 73 +++++++++++++++++- .../tabs/helm-release-helper.service.ts | 2 - .../helm-release-summary-tab.component.html | 9 +-- .../helm-release-summary-tab.component.scss | 7 ++ .../helm-release-summary-tab.component.ts | 59 +++++++++++--- .../helm-release-values-tab.component.ts | 10 +-- custom-src/frontend/assets/nav-logo-icon.png | Bin 862 -> 1226 bytes custom-src/frontend/assets/nav-logo.png | Bin 1421 -> 3778 bytes .../ring-chart/ring-chart.component.scss | 3 +- 10 files changed, 147 insertions(+), 26 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html index 4a62aabc41..8daf5f9d9f 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html @@ -2,6 +2,7 @@ {{ row.name }} + + + Ports + +
+
{{ port.name }}
+
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index d60751bf79..6ad2f27516 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -1,11 +1,78 @@ -import { Component } from '@angular/core'; +import { Component, Input, OnDestroy } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Subscription } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; +import { AppState } from '../../../../../../store/src/app-state'; +import { entityFactory } from '../../../../../../store/src/helpers/entity-factory'; +import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { EntityServiceFactory } from '../../../../core/entity-service-factory.service'; import { CardCell } from '../../../../shared/components/list/list.types'; -import { HelmReleaseService } from '../../store/helm.types'; +import { PaginationMonitor } from '../../../../shared/monitors/pagination-monitor'; +import { KubeService } from '../../../kubernetes/store/kube.types'; +import { GetKubernetesServicesInNamespace } from '../../../kubernetes/store/kubernetes.actions'; +import { GetHelmReleases } from '../../store/helm.actions'; +import { helmReleasesSchemaKey } from '../../store/helm.entities'; +import { HelmRelease, HelmReleaseService } from '../../store/helm.types'; @Component({ selector: 'app-release-service-card', templateUrl: './helm-release-service-card.component.html', styleUrls: ['./helm-release-service-card.component.scss'] }) -export class HelmReleaseServiceCardComponent extends CardCell { } +export class HelmReleaseServiceCardComponent extends CardCell implements OnDestroy { + + private pRow: HelmReleaseService; + private svcSub: Subscription; + + private ports = []; + + @Input() set row(row: HelmReleaseService) { + this.pRow = row; + if (!this.svcSub && row) { + this.svcSub = this.fetchRelease(row.endpointGuid, row.releaseTitle).pipe( + switchMap((release: any) => { + const action = new GetKubernetesServicesInNamespace(row.endpointGuid, release.namespace); + const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); + return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; + }), + filter(entities => !!entities), + map((services: any) => services.find(service => service.metadata.name === row.name)) + ).subscribe(service => { + console.log('SERVICE: ', service); + this.ports = service.spec.ports; + }); + } + + + } + get row() { + return this.pRow; + } + + + constructor( + private store: Store, + private esf: EntityServiceFactory + ) { + super(); + } + + ngOnDestroy() { + if (this.svcSub) { + this.svcSub.unsubscribe(); + } + } + + private fetchRelease(endpointGuid: string, releaseTitle: string) { + const action = new GetHelmReleases(); + const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(helmReleasesSchemaKey)); + const svc = getPaginationObservables({ store: this.store, action, paginationMonitor }); + + + return svc.entities$.pipe( + map((items: HelmRelease[]) => items.find(item => item.guid === `${endpointGuid}:${releaseTitle}`)) + ); + } + +} diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts index ad0215fa28..5b62b9191b 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts @@ -65,8 +65,6 @@ export const parseHelmReleaseStatus = (res: string): HelmReleaseStatus => { } }; - console.log(res); - // Process let i = 0; while (i < lines.length) { diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html index 8fa2b7eb05..4a9a5f39f0 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html @@ -39,15 +39,14 @@ - - + - + - + - + diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss index 952b0aaac5..c759665b86 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss @@ -8,4 +8,11 @@ margin-top: 0; } } +} + +.helm-release-summary { + + &__chart-tile { + width: 50%; + } } \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index bc07fe1050..8052ca25f4 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable, of as observableOf } from 'rxjs'; +import { Observable, of as observableOf, combineLatest } from 'rxjs'; import { map, startWith, tap } from 'rxjs/operators'; import { ClearPaginationOfType } from '../../../../../../../store/src/actions/pagination.actions'; @@ -12,6 +12,12 @@ import { ConfirmationDialogService } from '../../../../../shared/components/conf import { helmReleasesSchemaKey } from '../../../store/helm.entities'; import { HelmReleaseHelperService } from '../helm-release-helper.service'; +const podErrorStatus = { + Failed: true, + CrashLoopBackOff: true, + ErrImgPull: true, +}; + @Component({ selector: 'app-helm-release-summary-tab', templateUrl: './helm-release-summary-tab.component.html', @@ -44,8 +50,16 @@ export class HelmReleaseSummaryTabComponent { } ]; - // Blue: #00B2E2 - // Yellow: #FFC107 + public podsChartColors = [ + { + name: 'OK', + value: '#4DD3A7' + }, + { + name: 'Error', + value: '#E7727D' + } + ]; constructor( public helmReleaseHelper: HelmReleaseHelperService, @@ -53,19 +67,23 @@ export class HelmReleaseSummaryTabComponent { private confirmDialog: ConfirmationDialogService, private httpClient: HttpClient, ) { - this.isBusy$ = this.helmReleaseHelper.isFetching$; // Async fetch release status - this.helmReleaseHelper.fetchReleaseStatus().subscribe(data => { + const fetchStatus = this.helmReleaseHelper.fetchReleaseStatus(); + const isStatusBusy$ = fetchStatus.pipe(map(d => false)); + this.isBusy$ = combineLatest(this.helmReleaseHelper.isFetching$, isStatusBusy$).pipe( + map(([a, b]) => a || b) + ); + + fetchStatus.subscribe(data => { const chart = []; - console.log(data); Object.keys(data.pods.status).forEach(status => { chart.push({ name: status, value: data.pods.status[status] }); }); - this.podsChartData = chart; + this.podsChartData = this.collatePodStatus(data); this.containersChartData = [ { @@ -80,6 +98,30 @@ export class HelmReleaseSummaryTabComponent { }); } + private collatePodStatus(data: any): any { + let okay = 0; + let error = 0; + + Object.keys(data.pods.status).forEach(status => { + if (podErrorStatus[status]) { + error++; + } else { + okay++; + } + }); + + return [ + { + name: 'OK', + value: okay + }, + { + name: 'Error', + value: error + } + ]; + } + public deleteRelease() { this.confirmDialog.open(this.deleteReleaseConfirmation, () => { // Make the http request to delete the release @@ -87,9 +129,6 @@ export class HelmReleaseSummaryTabComponent { const deleting$ = this.httpClient.delete(`/pp/v1/helm/releases/${endpointAndName}`); this.loadingMessage = 'Deleting Release'; this.isBusy$ = deleting$.pipe( - tap(d => { - console.log(d); - }), map(d => false), startWith(true), ); diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts index 3759231456..2d28e1e570 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts @@ -16,7 +16,7 @@ export class HelmReleaseValuesTabComponent { constructor(public helmReleaseHelper: HelmReleaseHelperService) { this.values$ = helmReleaseHelper.release$.pipe( - map(release => { + map((release: any) => { if (release.config.raw) { return this.hidePasswords(release.config.raw); } else { @@ -27,10 +27,10 @@ export class HelmReleaseValuesTabComponent { } private hidePasswords(values: string): string { - let mask = values.replace(new RegExp('(PASSWORD: [a-zA-Z0-9_\-]*)', 'gm'), 'PASSWORD: **********'); - mask = mask.replace(new RegExp('(password: [a-zA-Z0-9_\-]*)', 'gm'), 'password: **********'); - mask = mask.replace(new RegExp('(SECRET: [a-zA-Z0-9_\-]*)', 'gm'), 'SECRET: **********'); - mask = mask.replace(new RegExp('(secret: [a-zA-Z0-9_\-]*)', 'gm'), 'secret: **********'); + let mask = values.replace(new RegExp('(PASSWORD: [\.a-zA-Z0-9_\-]*)', 'gm'), 'PASSWORD: **********'); + mask = mask.replace(new RegExp('(password: [\.a-zA-Z0-9_\-]*)', 'gm'), 'password: **********'); + mask = mask.replace(new RegExp('(SECRET: [\.a-zA-Z0-9_\-]*)', 'gm'), 'SECRET: **********'); + mask = mask.replace(new RegExp('(secret: [\.a-zA-Z0-9_\-]*)', 'gm'), 'secret: **********'); return mask; } } diff --git a/custom-src/frontend/assets/nav-logo-icon.png b/custom-src/frontend/assets/nav-logo-icon.png index fbcc1430b83ed6341448aba4d3c7e779cead6c17..bcff369d203474cb98c628062e0d5b7776b8600a 100644 GIT binary patch delta 1206 zcmV;n1WEhe2FeMLB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pF8FWQhbW?9;ba!ELWdLwtX>N2bZe?^JG%heMF*h@ApJM<31UgAX zK~z{r-Ir}>m1P*m=M0uFDTQRDhW2r=K+)Bt7bRa}A|wzW_kG>h z%gyutl%-Osk|*daC7ePFrNsYhlSwou?RuK^rtcrG>|;n7)ZbvG-_x z3Y$p8o`70Q4}XfVC$mu+O@dA}mKdLe4e%zs2%~U6jhzhh;VbAJigxioVP3Fup71Bz zXj~8tYG8mhH9L)Y-IiiSTshQ{bJ=qHVWus6V%hJX?Ff-Aiq3|+_|>EZ^AffSDlDX+ z$Qe!iZmV$zVmF!@#E}-obn0&6Q}M0lTW0fz&;#GXr++XVcezWYZ3D zh{cVd*ioz#6zkk#bg5(+4qD9{K z@C?H>*ncnKHPA!<794RqM6Byl`rUFWFUkndIK$m)#iKK z_Ng9C=4-%s{k=&(NIZ-!O62T;SvEdy8>xg}VWRnebUQ?hUo~GF#_R7*@0c9#EvzePP^CNo8*JU+keZL0ZIsDLhWIWb=WAJ|m$ zRqjwC#`oYFZm+*L$v7h-G07(PV&-7Ghdj==6{G8NJ#2?}K=1s%p$He#YHif#FSw4| z-%n-`BNE-aHcrGmfJqETVpoFun7|%|<**Q%z`gS&#(H)(TaPj3JBRT%nM5RZ5L{5i z>3@#i2;<=?=I_HH_*?5~G1unw-8tP_sK-oOYsVDcWHgAxP1wXDULOrDFhljQW8e*z z^wyfkST~M7GCqONEJlwWT!W6$r ziR2yIjh*g{Faf zW?J#gC)aR94c#QFH2{;%o!dX^2$3y{=|}56eih#b?z9yna&(II>!BKVp~jY+)PFOZ z97l+#DEbzpk7yr!$T0R-K_W(%Of8(Z+?z1lmNR0>?;mx92nqjG8XU(uG46#gU@kn5 z%UufCXPN8g?Q_uE#EJ3GFvS&%h-Hr)M~Hl4O-E)KfB!32COGiWi000000Qp0^e*gdg32;bRa{vGf6951U69E94oEQKA z00(qQO+^Re0~`-60>l$7g#Z8o>q$gGR9M69m|KXIRTRg6dmMGt%rRwjz!a6jv>H$f zLlIx{AqYgo=P*&i2Mh7hC+&hyN|dN?MLk;i;CKlIFD0UuLVt>v%qVY}*Gxr6e!eVW zGmf6~&H2teodpNJefD1e_1|mnwb$Ak8q$!46qZy5h&lH{mw1NAp`WM2~eG9&PGkss9l>PB^?IWuN9mPv5@Qv%{)g@C!MexkPY;L*oR-&z*Qv-VDYQV+!UR&`j0`9}PIqzr7DHtuVnIr?hy zDRxzCX_}7R0IWFvFf0<2Kdm$&P~ax&aK1)cAqA1(*wr1TF$i>iYmGrQdsL z;|WjRuHdQ3H%@5{i?80ak)2F88Exl zxuG*fU7h85)hp^(>a?r?c`5of)=F|o!RWrJE>cJIfYqw5P%lL8e=l_A*Glr0Dr?4` zQsN(Rh#ynWsrRDq!@%AZ*yGE{I%h0L_NZ@XrGK01p|W!Ki!~6t7DYy$4xh^kjG@o6 z-CXr$D5byDFMy@MePB#PZVNCxe#3x)Ko@WaxDH%QDc$M;@_XO~V0B9Azp`t}V=?;! z>hNCZ4^?*t#@>2W<}DEjMn-wJfbQ&Adewbu|9anb)@6I{u)0+3$cx*k++S4}tNXL@ zA3*ozjd7HEqQI5*m)=z2WjzkO3`~#5f!Bdi+2g>kz;@tZR`E2XAq}awJOW|*K;j$d R#ccop002ovPDHLkV1i+%mz@9r diff --git a/custom-src/frontend/assets/nav-logo.png b/custom-src/frontend/assets/nav-logo.png index 6d860e7023e527a5c93235aafac1fde1cdf33b23..9f4b0afb8425f6e8056f39350d995f28abcd58e7 100644 GIT binary patch literal 3778 zcmV;z4n6USP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^PVZl7ZS01h%qL_t(|UhSK8)HPKV#wiI21yMvo8bv|6 zL_!)BP)ZPy5YZ*LC_zFHkVOdy%8~_0hmuN4D=DCKN=ZwHgi_=0*=O(JOx$?y_pSB$ z$2@C2>zo~D=6&yd?#wxR&P>#yLx&C>I&|pJp+koWgZJNme<8RV{3E;$0N3*2BZ0%r3s;1*yUT$6890@jtsp)hTocaat_u7Z zbGlgq-#m)BDSHw)YtY?8gE3P=oohaKRS?H@&f@M0YB|%my97QRejR7P{}~`0P03wM?yj)|F)jn2(@|*uPP3ec`hq-N)iz+v z!#?&IxVVS=g7l3w#&H7N0Um7@B7-65<9$d8d=hiu*HB}BYgWSAp4U&%608t{J};TY z)$nKVbh8qUvOzsSeD0K1;#dzEu-8cLLKgrX`&VWqtm8S?C$l&oN)#`eh2P^L=)cL% zfv+KG`X?n<@gI0N+#Tu%a;>Kruy2F$<9^V6x_at65fv96gyCBnHUv%o;N-Hu{TEO_ zmWSkU#Eq8W7bi1FR-%6o|pucYkfc}?~%doM#8z8nz zN-3`N_9YJ)uuDO^OTG}?5pIW|k2yCP^>O+NxFSds(#qU0`4bUOrnv$y44hyLh-MtGV&Jg?ET zhq-SPx8H$x!IR*&a1q>-*uWkjXI~$y+iB0~2eAc|2PCSA-CY6x5cC=DGGH%}+{In) zDuHjE6e`|zyPu%nOrhR_7O>-oXP-|Sh{YH41#g9QJ*;?}pkq34h}(5RPmxT+^e_Z{ zkK#G-)dd}PWjas?@%>Vo-SMJ-xQ7kc!wPzfWEPKELI!+?rBHE)+aYM3^X}9wHu2bK zf@Z+P2v4^l1O12LkubjZ#5MD!Q0b(I&n^Om{W2$nM zyGr2GVk96j_Y<_;mB1iD@08rdh3={VzZO3Rx8us2--`1o$INgm_<|f@eb2crnZQ9Qf*jJ~5fak(ST{#C28HbYn)iFchBfumO8mLGPc;<~i}{Vko@f zb}s0cKHlcBkp;a$8aEmc?7Q$%_%+P(DobArN4u)ge#PAt5L!=h@S?jKAQpoDgS!ma znnP{u^h3}& z9)OE^JOur)4ak76F6dK}SsZ5xEg%*GzT92%#k6BZcgZ)bpbto9bG?C2b8g{Tw@X30 ztwp1tsoQ9RMr^nGz%hYwcS2P^wuVQ;x8SveX7oer*lT(24eqLd&_aqk=5==k_(Ra= zyUTzbAI@9uDuM6IDO9}T_H4;aSB4z;v{(|RbGw(IPf6}#2X|F~|IQR{h0VKZb3w;d zX&F0!13MS=&Ts<{8L&gpkGrb`zPg}8gjZTZ3y8&(=Vkb)^WSoSa#2fvSV8NqBb;do z8Swoug^KIkt_#|xxjHNg+oCYP zi?#h9i>HvCLsyxc!r7iz3fcy?fKUi}Wp`D8KLq`>yGr1z3wrHj8aA{f2li&(K4H;R z)d}7x2hh4IYSP0BI$y+=KUVr5f$?0!3IMbfK@0S6NUjoN5_DUwurC<%EX_2uSGj#F z)RV*Ypv{A5%P)hz%0%*21REfO)aij86V+w;$b=VyGac=Kn19o1=))%7ssU9?~;wcRf zi`!$POo{KV6tag$A?U_JHb6|%HldfG_1U+59$Le+33^hf|2uxXf)=H^{l|2p@)-#D zj!mKBH*Uv+mwIizA!q^nr!ek{g1%ENl&AI-U>}#<_W6%V(0Q?YC@|*a5UD`_ z7N{$<+8NxZ58UR`w6;LQcekCi4@lD$d8EgkexAAA>9#!j2M{YT+vTsl;?LvD&XG43GOuL$& z;|{kMw3F6AC|s2YgMij$_+sRwn~4()$TH2mx6Xz z34B`2#1pgfYAj&u2O-!#71~nJmH_C-CLi19OWSr8FBg*Zf1X_BBi~NYqX3~ropgNR z6^X;r*wJ(s$F3r z+O|CjTHFQn1Tdd+Xi5@qooJ!Fl8*^N>jB3$shc+G)5eeITo_ZDPSB%(4^I0nVvZ^C zT?O}rdqGVp&VtuNO)(yY_d&gubq+ifYMW!@JwE(;w5;P@=B`$dl0P8~=y9&SGe;YO zzQP7%z!w6xH!KADA@I%JCEp-H*C9d2V$k+shbFgm-inI{4_U=kVm8&^+2)b>X=(Z_j zbKTe}78{oGO;Upd{iwT2;ETtyZBvf*Q>b{z?GSWKlVaDHEl}MTf{v@QDmlWT-oEYr zczvl8^tb@w=_Ocu9f|h$u;}5Om{>Yyp2iLC2j%SM+?h zn4l|UJboV}=*DAte6y#JVNG`%uvNwsOmws%=yFRZ{aD=D%eu57=u6#>A536h_s52@ zPSE2D1h@i>EA?Yg_1(U&TM6W7+OihB*T+5&m-TR8kp7g$fI))R<`(U?)Am9wxMBmn zsw9=y_OauIPFtMP4ia?hq7{F&4nV;XRfN=wAuTZJUFjA!%e! zL952yw!19KT$Oj<1bngU%odWg@!+MLh6vap=++ky1Y(*7<@0=POI6)_W=~O02>MQU z8L;DxZSA{m6Z9U*^}!E{ek>R}L60x+Vd{#z32c<~@Vy6B6{rB8_6}AC9~k zXIwGtxyjR0(mC90pzS#F<^Dc7mCvj#ufuo(LR}K>1NGjZUZ8!+`4GGWsv2TX4o88$ z%nA7g4ts9$Ize|B41Jjs@(UdH+~jqF?l2hoGAHC0IPAH}>jd3lF!W_k$S-i%bCcHz sy2D`T%bbv3;IQW=uM_kH!$cGP53Juefl%61RsaA107*qoM6N<$f|r0qTmS$7 literal 1421 zcmcJP*;f(>7{yUi8k5lDIwg}*DQVK8iBeiByNG30uDMl;xRl~p?v$t8VgdpcHA`V~ zIpi`}NoM1cj+t3xZn@@4WVjWHro(?QPjk^kVy7*0Kk?40@~TjyLCPURItqn43w4~_3NGxX??r{}wQ+kegxY>0WP*(8FlEri}jn3Q) zeLFF~=TstkVn+572n}h|!SW%rRtG7K4x}=jDrplmTTU#dVIQXtfrvnI`v%GdkMFjJ zd(#jp&dx_+wd<@EUY!D9w(1~-^zH@JOhtR6PD$^?bBlM$e>mxv*y@0!0HvXk$r*Ye zs};}dh>isP053Af~HAG*KG0phm=; zTg@iMW#jo5l{acvFhGt4ckkm7G!G~|3q`{2;e!!S(@O`rPP@MjgNtV*ZJLKP)O6`f zRV%e{iZ(;+U!6J6Kjz!-Hl|PA_t6X0qL~3w+>f;Na$^5^6QU>OZDTWoH7RQiE6v@E zX<`pt{*V{lQxY-65TvAT(ZV4E`o}N9+z(j=*;61ig&K@ku~N`tE^*ramwd6_7vzMZ z6n~t1zl^VhWESA%ubvZ}`3GbzH&)r-ofl!lzq<08rwAqf+Qop&pE6=|1@-Bn6?*PM zXm8XXSAiZm)7W4#iiS`_B?~E7Dleay*SaPH7Q&xoiE@019;-2Qdp6=n!&H}c962B( z*xN6{QdZQ((hM&@GKR}Myh!znnu`UK2w_&&H8(41`0^FI(y!N2b4mRng}Pv@)EhOv zvr|^4{XnwlulxpjEa-Jnr<&9#wIXa}aORZ*m3KS2ye62CkGiE{>25YCnrW9Hus|BC zT8{$dPot4n39kFC$aalU7Ef}sR`yllgWx643x6&g<(myGLc_$2xozKy@UAbI7$nGL z3>nA|8(y{{EyzqYZp}X0w590K#uybTsuxc+b}bz9AALHS?{U#&XO%S(f@OwOwfY%6 zJUEE*qYAn`jn_k97s#A1ipG9a7F`6F0XX@0Z5*h13qzAM!*nQm?=S_1-^s_6UZ+4cWeeFKf6L&pDJ&Iv&XWujklvpJ%ppJ0QU)bUu7WhmU z7khsV@58HG0DHw$Nwv{KtNM+)T{ww)UgR8i?qQYm44_8GPb<(OhB<1&m{PEsSfU3H zx)#}pU;Y`vxsnr?_%?{`%)Xv7rK9A&y)-FaKBq6F38%Npd=M$4`%pZ)!NWgzH6&*p2%CFuIUFr4Lw diff --git a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss index f847591977..b52e50489e 100644 --- a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss +++ b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss @@ -10,7 +10,7 @@ .advanced-pie-legend { display: flex; flex-direction: column; - margin-right: 36px; + margin-right: 24px; top: 0; transform: none; @@ -19,6 +19,7 @@ } .total-label { + font-size: 18px; margin: -8px 0 0; } From 9c9898a493f28f485d20d0eff50cb858ab03053e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 07:08:59 -0500 Subject: [PATCH 02/34] Neil's fixes --- .../helm-release-service-card.component.ts | 4 +- .../kubernetes-svcproxy.component.html | 8 + .../kubernetes-svcproxy.component.scss | 15 ++ .../kubernetes-svcproxy.component.spec.ts | 25 +++ .../kubernetes-svcproxy.component.ts | 93 ++++++++++ .../plugins/kubernetes/kube_svc_proxy.go | 160 ++++++++++++++++++ 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts create mode 100644 src/jetstream/plugins/kubernetes/kube_svc_proxy.go diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index 6ad2f27516..7de422b085 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -30,9 +30,9 @@ export class HelmReleaseServiceCardComponent extends CardCell { - const action = new GetKubernetesServicesInNamespace(row.endpointGuid, release.namespace); + const action = new GetKubernetesServicesInNamespace(row.endpointId, release.namespace); const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; }), diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html new file mode 100644 index 0000000000..ca33850770 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html @@ -0,0 +1,8 @@ + +

Service Proxy

+
+ + + + + diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss new file mode 100644 index 0000000000..ff32c05101 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss @@ -0,0 +1,15 @@ +.kube-dashboard { + border: 0; + display: flex; + height: 100%; + width: 100%; +} + +.kube-dashboard__hidden { + visibility: hidden; +} + +.kube-dashboard__search { + display: inline; + font-size: 16px; +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts new file mode 100644 index 0000000000..78671a16f0 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy.component'; + +describe('KubernetesServiceProxyComponent', () => { + let component: KubernetesServiceProxyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesServiceProxyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesServiceProxyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts new file mode 100644 index 0000000000..6d12f4f83b --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts @@ -0,0 +1,93 @@ +import { BehaviorSubject, Observable } from 'rxjs'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; +import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { ActivatedRoute } from '@angular/router'; +import { KubernetesService } from '../services/kubernetes.service'; +import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-kubernetes-svcproxy', + templateUrl: './kubernetes-svcproxy.component.html', + styleUrls: ['./kubernetes-svcproxy.component.scss'], + providers: [ + { + provide: BaseKubeGuid, + useFactory: (activatedRoute: ActivatedRoute) => { + return { + guid: activatedRoute.snapshot.params.endpointId + }; + }, + deps: [ + ActivatedRoute + ] + }, + KubernetesService, + KubernetesEndpointService, + ] +}) +export class KubernetesServiceProxyComponent implements OnInit { + + @ViewChild('kubeDash', {read: ElementRef}) kubeDash: ElementRef; + + source: SafeResourceUrl; + isLoading$ = new BehaviorSubject(true); + + href = ''; + + public breadcrumbs$: Observable; + + constructor(public kubeEndpointService: KubernetesEndpointService, private sanitizer: DomSanitizer, public renderer: Renderer2) { } + + ngOnInit() { + const guid = this.kubeEndpointService.baseKube.guid; + + let href = window.location.href; + const index = href.indexOf('svcproxy'); + href = href.substr(index + 9); + console.log(href); + this.href = href; + const url = `/pp/v1/kubesvc/${guid}/${href}/`; + console.log(url); + this.source = this.sanitizer.bypassSecurityTrustResourceUrl(url); + console.log(window.location); + + this.breadcrumbs$ = this.kubeEndpointService.endpoint$.pipe( + map(endpoint => ([{ + breadcrumbs: [ + { value: endpoint.entity.name, routerLink: `/kubernetes/${endpoint.entity.guid}` }, + ] + }]) + ) + ); + } + + iframeLoaded() { + const iframeWindow = this.kubeDash.nativeElement.contentWindow; + console.log('iframe loaded'); + this.isLoading$.next(false); + + iframeWindow.addEventListener('hashchange', () => { + console.log('iframe hashchange'); + console.log(iframeWindow.location); + console.log(this.href); + + if (this.href) { + let h2 = decodeURI(this.href); + h2 = decodeURI(h2); + + h2 = h2.replace('%3F', '?'); + h2 = h2.replace('%3D', '='); + console.log(h2); + h2 = '#!' + h2; + console.log('Changing location hash'); + iframeWindow.location.hash = h2; + this.href = ''; + } + }); + + } + +} diff --git a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go new file mode 100644 index 0000000000..d62154a6fb --- /dev/null +++ b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go @@ -0,0 +1,160 @@ +package kubernetes + +import ( + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/rest" +) + +// GET /api/v1/namespaces/{namespace}/pods/{name}/proxy + +// http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/. + +//GET /api/v1/namespaces/{namespace}/services/{name}/proxy + +func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { + log.Info("kubeServiceProxy request") + + // c.Response().Header().Set("X-FRAME-OPTIONS", "sameorigin") + + cnsiGUID := c.Param("guid") + namespace := c.Param("ns") + serviceName := c.Param("svc") + servicePortName := c.Param("port") + userGUID := c.Get("user_id").(string) + + var p = k.portalProxy + + log.Debug(c.Request().RequestURI) + + var prefix = "/pp/v1/kubesvc/" + cnsiGUID + "/" + namespace + "/" + serviceName + "/" + servicePortName + "/" + + path := c.Request().RequestURI[len(prefix):] + + log.Info(path) + + cnsiRecord, err := p.GetCNSIRecord(cnsiGUID) + if err != nil { + //return sendSSHError("Could not get endpoint information") + return errors.New("Could not get endpoint information") + } + + // Get token for this users + tokenRec, ok := p.GetCNSITokenRecord(cnsiGUID, userGUID) + if !ok { + //return sendSSHError("Could not get endpoint information") + return errors.New("Could not get token") + } + + // Make the info call to the SSH endpoint info + // Currently this is not cached, so we must get it each time + apiEndpoint := cnsiRecord.APIEndpoint + log.Debug(apiEndpoint) + target := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", apiEndpoint, namespace, serviceName, servicePortName, path) + targetURL, _ := url.Parse(target) + targetURL = normalizeLocation(targetURL) + + log.Infof("Target URL: %s", targetURL) + + config, err := getConfig(&cnsiRecord, &tokenRec) + if err != nil { + return errors.New("Could not get config for this auth type") + } + + log.Info("Config") + log.Info(config.Host) + log.Info("Making request") + req := c.Request() + w := c.Response().Writer + log.Info("%v+", req) + + loc := targetURL + loc.RawQuery = req.URL.RawQuery + + // If original request URL ended in '/', append a '/' at the end of the + // of the proxy URL + if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { + loc.Path += "/" + } + + log.Info(loc) + + // From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP: + // Redirect requests with an empty path to a location that ends with a '/' + // This is essentially a hack for http://issue.k8s.io/4958. + // Note: Keep this code after tryUpgrade to not break that flow. + if len(loc.Path) == 0 { + log.Info("Redirecting") + var queryPart string + if len(req.URL.RawQuery) > 0 { + queryPart = "?" + req.URL.RawQuery + } + w.Header().Set("Location", req.URL.Path+"/"+queryPart) + w.WriteHeader(http.StatusMovedPermanently) + return nil + } + + // if transport == nil || wrapTransport { + // h.Transport = h.defaultProxyTransport(req.URL, h.Transport) + // } + + transport, err := rest.TransportFor(config) + if err != nil { + log.Info("Could not get transport") + return err + } + + log.Info(transport) + + // WithContext creates a shallow clone of the request with the new context. + newReq := req.WithContext(req.Context()) + //newReq := req.WithContext(context.Background()) + newReq.Header = utilnet.CloneHeader(req.Header) + newReq.URL = loc + + // Set auth header so we log in if needed + if len(tokenRec.AuthToken) > 0 { + newReq.Header.Add("Authorization", "Bearer "+tokenRec.AuthToken) + log.Info("Setting auth header") + } + + proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: loc.Scheme, Host: loc.Host}) + proxy.Transport = transport + proxy.FlushInterval = defaultFlushInterval + proxy.ModifyResponse = func(response *http.Response) error { + log.Debugf("GOT PROXY RESPONSE: %s", loc.String()) + log.Debugf("%d", response.StatusCode) + log.Debug(response.Header.Get("Content-Type")) + + log.Debugf("%v+", response.Header) + response.Header.Del("X-FRAME-OPTIONS") + response.Header.Set("X-FRAME-OPTIONS", "sameorigin") + log.Debug("%v+", response) + return nil + } + + log.Errorf("Proxy: %s", target) + + // Note that ServeHttp is non blocking and uses a go routine under the hood + proxy.ServeHTTP(w, newReq) + + // We need this to be blocking + + // select { + // case <-newReq.Context().Done(): + // return newReq.Context().Err() + // } + + log.Errorf("Finished proxying request: %s", target) + + return nil +} From 998f5c6f06cd63f366c9c15bcc8d39ce3d1a9724 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 12:10:50 -0500 Subject: [PATCH 03/34] FIXES --- .../demo-helper/demo-helper.component.html | 9 +++ .../frontend/app/custom/helm/helm.module.ts | 2 +- .../helm-release-service-card.component.html | 10 --- .../helm-release-service-card.component.ts | 73 +------------------ .../custom/kubernetes/kubernetes.module.ts | 2 + 5 files changed, 15 insertions(+), 81 deletions(-) diff --git a/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html b/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html index 4f7f70ff90..4a8cae66cf 100644 --- a/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html +++ b/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html @@ -3,6 +3,15 @@

SUSECON '19 Demo

+ +

Cloud Foundry

+ +
    +
  • https://api.10.86.2.125.xip.io
  • +
+ +
+

Helm Repositories

    diff --git a/custom-src/frontend/app/custom/helm/helm.module.ts b/custom-src/frontend/app/custom/helm/helm.module.ts index ca818ffc25..af13987c32 100644 --- a/custom-src/frontend/app/custom/helm/helm.module.ts +++ b/custom-src/frontend/app/custom/helm/helm.module.ts @@ -82,7 +82,7 @@ import { RepositoryTabComponent } from './tabs/repository-tab/repository-tab.com HelmReleaseValuesTabComponent, HelmReleasePodsTabComponent, HelmReleaseServicesTabComponent, - KubernetesServicePortsComponent + KubernetesServicePortsComponent, ], providers: [ ChartsService, diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html index 547294936e..3f71b45e59 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html @@ -2,7 +2,6 @@ {{ row.name }} - - - Ports - -
    -
    {{ port.name }}
    -
    -
    -
    \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index 7de422b085..d60751bf79 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -1,78 +1,11 @@ -import { Component, Input, OnDestroy } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { Subscription } from 'rxjs'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { Component } from '@angular/core'; -import { AppState } from '../../../../../../store/src/app-state'; -import { entityFactory } from '../../../../../../store/src/helpers/entity-factory'; -import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; -import { EntityServiceFactory } from '../../../../core/entity-service-factory.service'; import { CardCell } from '../../../../shared/components/list/list.types'; -import { PaginationMonitor } from '../../../../shared/monitors/pagination-monitor'; -import { KubeService } from '../../../kubernetes/store/kube.types'; -import { GetKubernetesServicesInNamespace } from '../../../kubernetes/store/kubernetes.actions'; -import { GetHelmReleases } from '../../store/helm.actions'; -import { helmReleasesSchemaKey } from '../../store/helm.entities'; -import { HelmRelease, HelmReleaseService } from '../../store/helm.types'; +import { HelmReleaseService } from '../../store/helm.types'; @Component({ selector: 'app-release-service-card', templateUrl: './helm-release-service-card.component.html', styleUrls: ['./helm-release-service-card.component.scss'] }) -export class HelmReleaseServiceCardComponent extends CardCell implements OnDestroy { - - private pRow: HelmReleaseService; - private svcSub: Subscription; - - private ports = []; - - @Input() set row(row: HelmReleaseService) { - this.pRow = row; - if (!this.svcSub && row) { - this.svcSub = this.fetchRelease(row.endpointId, row.releaseTitle).pipe( - switchMap((release: any) => { - const action = new GetKubernetesServicesInNamespace(row.endpointId, release.namespace); - const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); - return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; - }), - filter(entities => !!entities), - map((services: any) => services.find(service => service.metadata.name === row.name)) - ).subscribe(service => { - console.log('SERVICE: ', service); - this.ports = service.spec.ports; - }); - } - - - } - get row() { - return this.pRow; - } - - - constructor( - private store: Store, - private esf: EntityServiceFactory - ) { - super(); - } - - ngOnDestroy() { - if (this.svcSub) { - this.svcSub.unsubscribe(); - } - } - - private fetchRelease(endpointGuid: string, releaseTitle: string) { - const action = new GetHelmReleases(); - const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(helmReleasesSchemaKey)); - const svc = getPaginationObservables({ store: this.store, action, paginationMonitor }); - - - return svc.entities$.pipe( - map((items: HelmRelease[]) => items.find(item => item.guid === `${endpointGuid}:${releaseTitle}`)) - ); - } - -} +export class HelmReleaseServiceCardComponent extends CardCell { } diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts index 63012671aa..1915af3b1c 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts @@ -86,6 +86,7 @@ import { KubernetesNamespacesTabComponent } from './tabs/kubernetes-namespaces-t import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component'; import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component'; import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy/kubernetes-svcproxy.component'; /* tslint:disable:max-line-length */ /* tslint:enable */ @@ -139,6 +140,7 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub KubeNamespacePodCountComponent, PodNameLinkComponent, NodePodCountComponent, + KubernetesServiceProxyComponent, ], providers: [ KubernetesService, From 29a15e9b7e5cec05a168f05eedfac96c92853bcc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 15:58:54 -0500 Subject: [PATCH 04/34] Fixes, docker AIO build fix --- .../app/custom/kubernetes/kubernetes.routing.ts | 17 +++++++++++++++++ .../app/custom/kubernetes/store/kube.types.ts | 4 ++++ deploy/Dockerfile.all-in-one | 2 +- .../Dockerfile.stratos-go-build-base.tmpl | 4 ++-- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts index 77d1c9d821..0a1fb0341d 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts @@ -1,3 +1,4 @@ +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy/kubernetes-svcproxy.component'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; @@ -167,6 +168,22 @@ const kubernetes: Routes = [{ } } ] +}, +{ + path: ':endpointId/svcproxy', + component: KubernetesServiceProxyComponent, + data: { + uiNoMargin: true + }, + children: [ + { + path: '**', + component: KubernetesServiceProxyComponent, + data: { + uiNoMargin: true + } + } + ] } ]; diff --git a/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts b/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts index 9c78513045..b0817e880e 100644 --- a/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts +++ b/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts @@ -1,3 +1,5 @@ +import { Observable } from "rxjs"; + export interface KubernetesInfo { nodes: {}; pods: {}; @@ -18,7 +20,9 @@ export interface KubeService { metadata: KubeServiceMetadata; status: ServiceStatus; spec: DeploymentSpec; + kubeService$?: Observable; } + export interface KubernetesStatefulSet { metadata: KubeServiceMetadata; status: ServiceStatus; diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 3f23b62a95..ee5a82601a 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -3,7 +3,7 @@ FROM splatform/stratos-aio-base:opensuse as builder COPY --chown=stratos:users *.json ./ COPY --chown=stratos:users gulpfile.js ./ -COPY --chown=stratos:users Gopkg.* ./ +#COPY --chown=stratos:users Gopkg.* ./ COPY --chown=stratos:users src ./src COPY --chown=stratos:users build ./build/ COPY --chown=stratos:users deploy/tools/generate_cert.sh generate_cert.sh diff --git a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl index 8b156d27e3..b26bfd694e 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl @@ -11,8 +11,8 @@ RUN zypper ref RUN zypper -n ref && \ zypper -n up && \ zypper in -y which tar git gcc curl wget -RUN wget https://storage.googleapis.com/golang/go1.9.7.linux-amd64.tar.gz && \ - tar -xzf go1.9.7.linux-amd64.tar.gz -C /usr/local/ && \ +RUN wget https://dl.google.com/go/go1.12.1.linux-amd64.tar.gz && \ + tar -xzf go1.12.1.linux-amd64.tar.gz -C /usr/local/ && \ mkdir -p /home/stratos/go/bin && \ mkdir -p /home/stratos/go/src From 8df21b7e21d07b80f9c0394232abac82e481b473 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 3 Apr 2019 21:58:09 -0500 Subject: [PATCH 05/34] Refinements --- .../create-release.component.html | 2 +- .../create-release.component.scss | 1 + .../kubernetes-service-ports.component.html | 2 +- src/jetstream/datastore/datastore.go | 13 +++++-- .../plugins/kubernetes/endpoint_config.go | 39 ++++++++++++++----- .../plugins/kubernetes/helm_client.go | 2 +- .../plugins/kubernetes/kube_dashboard.go | 6 +-- .../plugins/kubernetes/kube_svc_proxy.go | 12 +++++- src/jetstream/plugins/kubernetes/main.go | 2 + 9 files changed, 59 insertions(+), 20 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html index 3d8f081b1f..e009d50e47 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html @@ -32,7 +32,7 @@

    Enter YAML Value Overrides

    Values + [matTextareaAutosize]="true" cdkAutosizeMinRows="12" cdkAutosizeMaxRows="16"> diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss index b1b37d0268..1d498faabe 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss @@ -14,5 +14,6 @@ form { &__yaml { background-color: rgba(0, 0, 0, .1); font-family: 'Source Code Pro', monospace; + font-size: 14px; } } diff --git a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html index 35187acf75..94a0dfa883 100644 --- a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html @@ -8,7 +8,7 @@ - + {{ port.name}} {{port.name}} diff --git a/src/jetstream/datastore/datastore.go b/src/jetstream/datastore/datastore.go index 1b8110f8e0..1a1cf3997e 100644 --- a/src/jetstream/datastore/datastore.go +++ b/src/jetstream/datastore/datastore.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "os" + "path" "regexp" "strings" "time" @@ -64,7 +65,7 @@ const ( // SQLiteSchemaFile - SQLite schema file SQLiteSchemaFile = "./deploy/db/sqlite_schema.sql" // SQLiteDatabaseFile - SQLite database file - SQLiteDatabaseFile = "./console-database.db" + SQLiteDatabaseFile = "console-database.db" // Default database provider when not specified DefaultDatabaseProvider = MYSQL ) @@ -147,16 +148,20 @@ func GetConnection(dc DatabaseConfig, env *env.VarSet) (*sql.DB, error) { } // SQL Lite - return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB")) + return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB"), env.String("SQLITE_DB_DIR", ".")) } // GetSQLLiteConnection returns an SQLite DB Connection -func GetSQLLiteConnection(sqliteKeepDB bool) (*sql.DB, error) { +func GetSQLLiteConnection(sqliteKeepDB bool, sqlDbDir string) (*sql.DB, error) { if !sqliteKeepDB { os.Remove(SQLiteDatabaseFile) } - db, err := sql.Open("sqlite3", SQLiteDatabaseFile) + dbFilePath := path.Join(sqlDbDir, SQLiteDatabaseFile) + + log.Infof("SQLite Database file: %s", dbFilePath) + + db, err := sql.Open("sqlite3", dbFilePath) if err != nil { return nil, err } diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index cf2fbe79a2..06893ebe52 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -3,6 +3,7 @@ package kubernetes import ( "encoding/json" "errors" + "fmt" "strings" log "github.com/sirupsen/logrus" @@ -15,7 +16,7 @@ import ( ) // GetConfigForEndpoint gets a config for the Kubernetes go-client for the specified endpoint -func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { +func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { log.Debug("GetConfigForEndpoint") @@ -36,7 +37,7 @@ func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*rest // Configure auth information authInfo := clientcmdapi.NewAuthInfo() - err := addAuthInfoForEndpoint(authInfo, token) + err := c.addAuthInfoForEndpoint(authInfo, token) config := clientcmdapi.NewConfig() config.Clusters[name] = cluster @@ -49,7 +50,7 @@ func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*rest } -func addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { log.Debug("addAuthInfoForEndpoint") log.Warn(tokenRec.AuthType) @@ -57,16 +58,18 @@ func addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.Tok switch { case tokenRec.AuthType == "gke-auth": log.Warn("GKE AUTH") - return addGKEAuth(info, tokenRec) + return c.addGKEAuth(info, tokenRec) case tokenRec.AuthType == AuthConnectTypeCertAuth, tokenRec.AuthType == AuthConnectTypeKubeConfigAz: - return addCertAuth(info, tokenRec) + return c.addCertAuth(info, tokenRec) + case tokenRec.AuthType == AuthConnectTypeAWSIAM: + return c.addAWSAuth(info, tokenRec) default: log.Error("Unsupported auth type") } return errors.New("Unsupported auth type") } -func addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { kubeAuthToken := &KubeCertAuth{} err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { @@ -80,15 +83,33 @@ func addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) e return nil } -func addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { gkeInfo := &GKEConfig{} err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) if err != nil { return err } - log.Warn("HERE") - info.Token = tokenRec.AuthToken return nil } + +func (c *KubernetesSpecification) addAWSAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + + awsInfo := &AWSIAMUserInfo{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) + if err != nil { + return err + } + + // NOTE: We really should check first to see if the token has expired before we try and get another + + // Get an access token + token, err := c.getTokenIAM(*awsInfo) + if err != nil { + return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + } + + info.Token = token + return nil +} diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 305da7a13f..2bbb9e8148 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -46,7 +46,7 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he return nil, nil, nil, errors.New("Can not get user token for endpoint") } - config, err := GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) + config, err := c.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) if err != nil { return nil, nil, nil, errors.New("Can not get Kubernetes config for specified endpoint") } diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index d24d596380..afcbb0b078 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -51,9 +51,9 @@ func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { } // Get the config for the certificate authentication -func getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { +func (k *KubernetesSpecification) getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { masterURL := cnsiRecord.APIEndpoint.String() - return GetConfigForEndpoint(masterURL, *tokenRecord) + return k.GetConfigForEndpoint(masterURL, *tokenRecord) } // Get the config for the certificate authentication @@ -159,7 +159,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { targetURL, _ := url.Parse(target) targetURL = normalizeLocation(targetURL) - config, err := getConfig(&cnsiRecord, &tokenRec) + config, err := k.getConfig(&cnsiRecord, &tokenRec) if err != nil { return errors.New("Could not get config for this auth type") } diff --git a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go index d62154a6fb..f4035a6500 100644 --- a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go +++ b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go @@ -59,13 +59,14 @@ func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { // Currently this is not cached, so we must get it each time apiEndpoint := cnsiRecord.APIEndpoint log.Debug(apiEndpoint) + absTarget := fmt.Sprintf("/api/v1/namespaces/%s/services/%s:%s/proxy/", namespace, serviceName, servicePortName) target := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", apiEndpoint, namespace, serviceName, servicePortName, path) targetURL, _ := url.Parse(target) targetURL = normalizeLocation(targetURL) log.Infof("Target URL: %s", targetURL) - config, err := getConfig(&cnsiRecord, &tokenRec) + config, err := k.getConfig(&cnsiRecord, &tokenRec) if err != nil { return errors.New("Could not get config for this auth type") } @@ -139,6 +140,15 @@ func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { response.Header.Del("X-FRAME-OPTIONS") response.Header.Set("X-FRAME-OPTIONS", "sameorigin") log.Debug("%v+", response) + + if response.StatusCode == 302 { + redirect := response.Header.Get("Location") + if strings.Index(redirect, absTarget) == 0 { + redirect = redirect[len(absTarget):] + response.Header.Set("Location", redirect) + } + + } return nil } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index c8b1f1553a..b4d004f945 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -152,6 +152,8 @@ func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { // Kubernetes Dashboard Proxy echoGroup.GET("/kubedash/ui/:guid/*", c.kubeDashboardProxy) echoGroup.GET("/kubedash/:guid/status", c.kubeDashboardStatus) + echoGroup.GET("/kubesvc/:guid/:ns/:svc/:port/*", c.kubeServiceProxy) + echoGroup.GET("/kubesvc/:guid/:ns/:svc/:port", c.kubeServiceProxy) // Helm Routes echoGroup.GET("/helm/releases", c.ListReleases) From 91ad7f5d40faa18cb6bccaae51058fb73c068050 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 4 Apr 2019 06:25:55 -0500 Subject: [PATCH 06/34] Tweaks --- .../kubernetes-service-ports.component.ts | 2 +- .../helm-release-summary-tab.component.ts | 13 +++---------- .../simple-usage-chart.component.ts | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts index 4910d35619..e38ab70603 100644 --- a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts @@ -36,6 +36,6 @@ export class KubernetesServicePortsComponent extends CardCell { - const isStatusBusy$ = fetchStatus.pipe(map(d => false)); - this.isBusy$ = combineLatest(this.helmReleaseHelper.isFetching$, isStatusBusy$).pipe( - map(([a, b]) => a || b) - ); - fetchStatus.subscribe(data => { - - this.podsChartData = Object.keys(data.pods.status).map(status => ({ - name: status, - value: data.pods.status[status] - })); + // Pods status (grouped) + this.podsChartData = this.collatePodStatus(data); + // Container Readiness this.containersChartData = [ { name: 'Ready', diff --git a/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts b/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts index b0d68ba8c8..d39e7d6217 100644 --- a/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts +++ b/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts @@ -30,7 +30,7 @@ export class SimpleUsageChartComponent { @Input() title = 'Usage'; - @Input() height = '250px'; + @Input() height = '160px'; @Input() thresholds: IChartThresholds = { danger: 85, From a255c19e54ce3f534e7c478a327b906128964eed Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:46:58 +0100 Subject: [PATCH 07/34] WIP: Build and gtag AIO image --- deploy/ci/build-images-stable.yml | 104 +++++++++--------- .../tasks/dev-releases/check-docker-image.yml | 36 ++++++ .../tasks/dev-releases/generate-tag-files.yml | 5 +- 3 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 deploy/ci/tasks/dev-releases/check-docker-image.yml diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 716824e395..c87c42eb10 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -1,5 +1,7 @@ # Stable image build pipeline -# This pipeline builds the stable Docker images for Docker Compose and AIO when the stable tag is updated +# This pipeline builds the stable Docker image for the AIO when the stable tag is updated +# It also tags this stable image with the tag of the release version - e.g. 2.3.0 +# The latest tag is also updated as this track 'stable' --- resource_types: - name: docker-image @@ -24,6 +26,18 @@ resources: private_key: ((github-private-key)) # Match stable tag tag_filter: "stable" + +# Artifacts +- name: image-tag + type: s3 + source: + bucket: ((minio-bucket)) + endpoint: ((minio-server-endpoint)) + regexp: temp-artifacts/release-(.*).tar + access_key_id: ((minio-access-key)) + secret_access_key: ((minio-secret-access-key)) + region_name: eu-central-1 + - name: aio-docker-image type: docker-image source: @@ -31,76 +45,62 @@ resources: password: ((docker-password)) repository: splatform/stratos # Docker Images -- name: jetstream-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-jetstream -- name: dc-db-migrator-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-db-migrator -- name: mariadb-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-mariadb - name: ui-dc-image type: docker-image source: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos-dc-console -groups: -- name: tests - jobs: - - build-dc-images - - build-aio-image jobs: -- name: build-dc-images +- name: generate-tag-files plan: - get: stratos trigger: true - - aggregate: - - do: - - put: jetstream-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: dev-build - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: dc-db-migrator-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: db-migrator - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: mariadb-dc-image - params: - dockerfile: stratos/deploy/db/Dockerfile.mariadb - build: stratos/deploy/db - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: ui-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.ui - build: stratos/ - target_name: prod-build - tag: stratos/deploy/ci/tasks/build-images/stable-tag - timeout: 2h30m - + - do: + - task: generate-tag + file: stratos/deploy/ci/tasks/dev-releases/generate-tag-files.yml + params: + TAG_SUFFIX: ((tag-suffix)) + - put: image-tag + params: + file: image-tag/*.tar + acl: public-read +- name: check-github + plan: + - get: stratos + passed: [generate-tag-files] + trigger: true + - get: image-tag + passed: [generate-tag-files] + params: + unpack: true + - do: + - task: build + privileged: true + timeout: 30m + file: stratos/deploy/ci/tasks/dev-releases/check-docker-image.yml + params: + DOCKER_REGISTRY: ((docker-registry)) + DOCKER_ORG: ((docker-organization)) + DOCKER_USERNAME: ((docker-username)) + DOCKER_PASSWORD: ((docker-password)) + IMAGE_NAME: stratos - name: build-aio-image public: true serial: true plan: - get: stratos + passed: [check-github] trigger: true + - get: image-tag + passed: [check-github] + params: + unpack: true - put: aio-docker-image params: build: stratos dockerfile: stratos/deploy/Dockerfile.all-in-one tag: stratos/deploy/ci/tasks/build-images/stable-tag + tas_as_latest: true + labels_file: image-tag/image-labels diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml new file mode 100644 index 0000000000..b5577bde56 --- /dev/null +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -0,0 +1,36 @@ +--- +platform: linux +inputs: +- name: stratos +- name: image-tag +image_resource: + type: docker-image + source: + # Generated using scripts/Dockerfile.stratos-ci + repository: splatform/stratos-ci-concourse + tag: "latest" + +run: + path: sh + args: + - -exc + - | + # Check that an image with the same Commit DOES NOT exist + ROOT_DIR=${PWD} + VERSION=$(cat image-tag/v2-version) + FULL_VERSION=$(cat image-tag/v2-alpha-tag) + GIT_TAG=$(cat image-tag/v2-tag) + STRATOS=${ROOT_DIR}/stratos + + # Make the Docker Auth token + AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" + TOKEN=`echo ${AUTH} | base64" + + URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list + + # Fetch the image tags + curl -s -H "Authorization: Basic ${TOKEN}" ${URL} + + echo "okay" + + diff --git a/deploy/ci/tasks/dev-releases/generate-tag-files.yml b/deploy/ci/tasks/dev-releases/generate-tag-files.yml index 7e3876cc8e..aa28250ffe 100644 --- a/deploy/ci/tasks/dev-releases/generate-tag-files.yml +++ b/deploy/ci/tasks/dev-releases/generate-tag-files.yml @@ -60,6 +60,9 @@ run: cat > build-args << EOF { "stratos_version": "${LATEST_TAG}" } EOF + cat > image-labels << EOF + { "commit": "${COMMIT_HASH}" } + EOF echo "Created v2-alpha-tag, v2-version and build-args." @@ -73,7 +76,7 @@ run: cat v2-alpha-tag echo "Creating tag file tar..." - tar -cf ${FILENAME}.tar v2-alpha-tag v2-version v2-tag v2-commit build-args ui-build-args + tar -cf ${FILENAME}.tar v2-alpha-tag v2-version v2-tag v2-commit build-args ui-build-args image-labels echo "Created tag file tar as ${FILENAME}.tar" echo "Generate tag files complete!" From a6a4f3aa984098f81aca3f9d8fbd11d8c06c1a17 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:51:55 +0100 Subject: [PATCH 08/34] Remove unused DC image --- deploy/ci/build-images-stable.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index c87c42eb10..0f842fb7d5 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -38,19 +38,13 @@ resources: secret_access_key: ((minio-secret-access-key)) region_name: eu-central-1 +# Docker Images - name: aio-docker-image type: docker-image source: username: ((docker-username)) password: ((docker-password)) repository: splatform/stratos -# Docker Images -- name: ui-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-console jobs: - name: generate-tag-files From 1fc5d9bc53600cbae18a68e2093db81ce76e8e68 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:59:18 +0100 Subject: [PATCH 09/34] Remove logging --- deploy/ci/build-images-stable.yml | 6 +++--- deploy/ci/tasks/dev-releases/check-docker-image.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 0f842fb7d5..9485aa1493 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -60,7 +60,7 @@ jobs: params: file: image-tag/*.tar acl: public-read -- name: check-github +- name: check-docker-image plan: - get: stratos passed: [generate-tag-files] @@ -85,10 +85,10 @@ jobs: serial: true plan: - get: stratos - passed: [check-github] + passed: [check-docker-image] trigger: true - get: image-tag - passed: [check-github] + passed: [check-docker-image] params: unpack: true - put: aio-docker-image diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index b5577bde56..436cdd1cc1 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -13,7 +13,7 @@ image_resource: run: path: sh args: - - -exc + - -ec - | # Check that an image with the same Commit DOES NOT exist ROOT_DIR=${PWD} @@ -24,7 +24,7 @@ run: # Make the Docker Auth token AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" - TOKEN=`echo ${AUTH} | base64" + TOKEN=`echo "${AUTH}" | base64` URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list From 0f60f2e3efcf6076c840b22b5574721c168b838e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 14:34:24 +0100 Subject: [PATCH 10/34] Fix docker org --- deploy/ci/build-images-stable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 9485aa1493..da3b200aa7 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -44,7 +44,7 @@ resources: source: username: ((docker-username)) password: ((docker-password)) - repository: splatform/stratos + repository: ((docker-organization))/stratos jobs: - name: generate-tag-files From 8d87a2a770598b0b45cd7ac586d87ac550312160 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 15:15:17 +0100 Subject: [PATCH 11/34] Debug --- deploy/ci/build-images-stable.yml | 7 +++++++ deploy/ci/tasks/dev-releases/check-docker-image.yml | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index da3b200aa7..d65888db0b 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -45,6 +45,7 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos + tag: stable jobs: - name: generate-tag-files @@ -60,6 +61,10 @@ jobs: params: file: image-tag/*.tar acl: public-read +- name: get-image-metadata + plan: + - get: stratos + trigger: true - name: check-docker-image plan: - get: stratos @@ -69,6 +74,8 @@ jobs: passed: [generate-tag-files] params: unpack: true + - get: aio-docker-image + passed: [generate-tag-files] - do: - task: build privileged: true diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index 436cdd1cc1..f8de49c5db 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -13,7 +13,7 @@ image_resource: run: path: sh args: - - -ec + - -c - | # Check that an image with the same Commit DOES NOT exist ROOT_DIR=${PWD} @@ -22,6 +22,10 @@ run: GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + ls ${ROOT_DIR} + ls /aio-docker-image + cat /aio-docker-image/docker_inspect.json + # Make the Docker Auth token AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" TOKEN=`echo "${AUTH}" | base64` From 8d5cea555a0b3c694223ac0f613aafb02c928766 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 15:29:13 +0100 Subject: [PATCH 12/34] Add missing input --- deploy/ci/build-images-stable.yml | 5 ----- deploy/ci/tasks/dev-releases/check-gh-release.yml | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index d65888db0b..1838762a0c 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -61,10 +61,6 @@ jobs: params: file: image-tag/*.tar acl: public-read -- name: get-image-metadata - plan: - - get: stratos - trigger: true - name: check-docker-image plan: - get: stratos @@ -75,7 +71,6 @@ jobs: params: unpack: true - get: aio-docker-image - passed: [generate-tag-files] - do: - task: build privileged: true diff --git a/deploy/ci/tasks/dev-releases/check-gh-release.yml b/deploy/ci/tasks/dev-releases/check-gh-release.yml index 84529c8419..55ad3f92f3 100644 --- a/deploy/ci/tasks/dev-releases/check-gh-release.yml +++ b/deploy/ci/tasks/dev-releases/check-gh-release.yml @@ -3,6 +3,7 @@ platform: linux inputs: - name: stratos - name: image-tag +- name: aio-docker-image image_resource: type: docker-image source: From 9ac8250347228012c179e7f4825acf5b34bb2e69 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 14:31:58 +0100 Subject: [PATCH 13/34] Add missing input --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index f8de49c5db..a5075db328 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -3,6 +3,7 @@ platform: linux inputs: - name: stratos - name: image-tag +- name: aio-docker-image image_resource: type: docker-image source: From 2f27d40aad09d3196c02f07f6c7a25547abf37d6 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 14:42:57 +0100 Subject: [PATCH 14/34] Fix docker image check --- .../tasks/dev-releases/check-docker-image.yml | 21 ++++---- .../tasks/dev-releases/docker-image-helper.sh | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+), 11 deletions(-) create mode 100755 deploy/ci/tasks/dev-releases/docker-image-helper.sh diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index a5075db328..ca0cc1783f 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -3,7 +3,6 @@ platform: linux inputs: - name: stratos - name: image-tag -- name: aio-docker-image image_resource: type: docker-image source: @@ -22,20 +21,20 @@ run: FULL_VERSION=$(cat image-tag/v2-alpha-tag) GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + COMMIT_HASH=$(cat image-tag/v2-commit) ls ${ROOT_DIR} - ls /aio-docker-image - cat /aio-docker-image/docker_inspect.json + + source ${STRATOS}/deploy/ci/tasks/dev-releases/docker-image-helper.sh - # Make the Docker Auth token - AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" - TOKEN=`echo "${AUTH}" | base64` + # Get the Commit Label for the image + COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` - URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list + echo "Current Docker Image has Commit $COMMIT" - # Fetch the image tags - curl -s -H "Authorization: Basic ${TOKEN}" ${URL} + cat $GIT_TAG + cat $FULL_VERSION + cat $VERSION + cat $COMMIT_HASH echo "okay" - - diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh new file mode 100755 index 0000000000..c961844c36 --- /dev/null +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Get commit label for a docker image + +function dockerMakeCurl() { + local URL=$1 + local MANIFEST=$2 + + if [ "$MANIFEST" == "true" ]; then + + if [ "$TOKEN" != "" ]; then + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL + else + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" $USER_AUTH $URL + fi + else + if [ "$TOKEN" != "" ]; then + curl --location -s -H "Authorization: Bearer $TOKEN" $URL + else + curl --location -s $USER_AUTH $URL + fi + fi +} + +function getDockerImageCommitLabel() { + + local REG=$1 + local USER=$2 + local PASS=$3 + local ORG=$4 + local IMAGE=$5 + local TAG=$6 + + local USER_AUTH="" + + if [ "$REG" == "docker.io" ]; then + echo "Getting TOKEN for docker.io access" + TOKEN=`curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$ORG/$IMAGE:pull" | jq -r .token` + AUTH_TYPE="Bearer" + REGISTRY_ADDRESS=https://registry.hub.docker.com + else + USER_AUTH="-u $USER:$PASS" + REGISTRY_ADDRESS=https://$REG + TOKEN="" + fi + + URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` + + COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` + echo "$COMMIT" +} From 12fc0055882f381df5b35ae4e3ee0f06edf6b0ff Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:25:08 +0100 Subject: [PATCH 15/34] Fix helper --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index c961844c36..3f64598821 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -34,7 +34,6 @@ function getDockerImageCommitLabel() { local USER_AUTH="" if [ "$REG" == "docker.io" ]; then - echo "Getting TOKEN for docker.io access" TOKEN=`curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$ORG/$IMAGE:pull" | jq -r .token` AUTH_TYPE="Bearer" REGISTRY_ADDRESS=https://registry.hub.docker.com From d7ce1b16ceb72842a257e1a19a5fc38fc74bd8c8 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:41:43 +0100 Subject: [PATCH 16/34] Debugging --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 11 +++++------ deploy/ci/tasks/dev-releases/docker-image-helper.sh | 9 +++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index ca0cc1783f..ab5610d942 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -23,18 +23,17 @@ run: STRATOS=${ROOT_DIR}/stratos COMMIT_HASH=$(cat image-tag/v2-commit) - ls ${ROOT_DIR} - source ${STRATOS}/deploy/ci/tasks/dev-releases/docker-image-helper.sh + cat $GIT_TAG + cat $FULL_VERSION + cat $VERSION + cat $COMMIT_HASH + # Get the Commit Label for the image COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` echo "Current Docker Image has Commit $COMMIT" - cat $GIT_TAG - cat $FULL_VERSION - cat $VERSION - cat $COMMIT_HASH echo "okay" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 3f64598821..3598b2f2db 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -43,7 +43,16 @@ function getDockerImageCommitLabel() { TOKEN="" fi + echo $TOKEN + echo $REG + echo $ORG + echo $IMAGE + echo $TAG + URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + + dockerMakeCurl $URL "true" | jq . + DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` From 10b1b72077265c4ae1afd90d3c456cd310d2ae43 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:48:11 +0100 Subject: [PATCH 17/34] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index ab5610d942..b2709892d7 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` + getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME echo "Current Docker Image has Commit $COMMIT" From 9176e46d4e01a85ba3d6d5808da59c8e2e0d8709 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:05:31 +0100 Subject: [PATCH 18/34] Fix --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 3598b2f2db..061cb0bc0b 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -6,10 +6,14 @@ function dockerMakeCurl() { local URL=$1 local MANIFEST=$2 + echo $URL + echo $MANIFEST + echo $TOKEN + if [ "$MANIFEST" == "true" ]; then if [ "$TOKEN" != "" ]; then - curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL else curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" $USER_AUTH $URL fi @@ -49,7 +53,8 @@ function getDockerImageCommitLabel() { echo $IMAGE echo $TAG - URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" + echo "URL: $URL" dockerMakeCurl $URL "true" | jq . From 715f18109be50cb4a83c9021f6f279902652696d Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:08:38 +0100 Subject: [PATCH 19/34] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index b2709892d7..fb1a5735bc 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME + getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME echo "Current Docker Image has Commit $COMMIT" From ca16246d1c8dda777b680c3698d37ff9ffcff09a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:12:37 +0100 Subject: [PATCH 20/34] Fix --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 061cb0bc0b..096a9d045d 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -6,10 +6,6 @@ function dockerMakeCurl() { local URL=$1 local MANIFEST=$2 - echo $URL - echo $MANIFEST - echo $TOKEN - if [ "$MANIFEST" == "true" ]; then if [ "$TOKEN" != "" ]; then From 207feb2b6fef5914e109fd4616ec60ad7fa4b5ca Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:17:06 +0100 Subject: [PATCH 21/34] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 4 ++++ deploy/ci/tasks/dev-releases/docker-image-helper.sh | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index fb1a5735bc..2b29ff9be4 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -35,5 +35,9 @@ run: echo "Current Docker Image has Commit $COMMIT" + if [ "$COMMIT" == "$COMMIT_HASH" ]; then + echo "Image has already been built and published for commit ${COMMIT_HASH}" + exit 1 + fi echo "okay" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 096a9d045d..66c62ba8de 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -43,14 +43,7 @@ function getDockerImageCommitLabel() { TOKEN="" fi - echo $TOKEN - echo $REG - echo $ORG - echo $IMAGE - echo $TAG - local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" - echo "URL: $URL" dockerMakeCurl $URL "true" | jq . From c6f1f8c404427225735303bb411d9b63aac82d63 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:25:56 +0100 Subject: [PATCH 22/34] Final fixes --- deploy/ci/build-images-stable.yml | 6 ++++-- deploy/ci/tasks/dev-releases/check-docker-image.yml | 6 ++++-- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 1838762a0c..26b9c99bd7 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -1,7 +1,7 @@ # Stable image build pipeline # This pipeline builds the stable Docker image for the AIO when the stable tag is updated # It also tags this stable image with the tag of the release version - e.g. 2.3.0 -# The latest tag is also updated as this track 'stable' +# The latest tag is also updated as this tracks 'stable' --- resource_types: - name: docker-image @@ -45,7 +45,7 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos - tag: stable + tag: "stable" jobs: - name: generate-tag-files @@ -82,6 +82,7 @@ jobs: DOCKER_USERNAME: ((docker-username)) DOCKER_PASSWORD: ((docker-password)) IMAGE_NAME: stratos + TAG_NAME: stable - name: build-aio-image public: true serial: true @@ -100,3 +101,4 @@ jobs: tag: stratos/deploy/ci/tasks/build-images/stable-tag tas_as_latest: true labels_file: image-tag/image-labels + additional_tags: image-tag/v2-version diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index 2b29ff9be4..9d93035a43 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME + COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME` echo "Current Docker Image has Commit $COMMIT" @@ -40,4 +40,6 @@ run: exit 1 fi - echo "okay" + echo "Docker Image has not been build from this commit" + + echo "OK" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 66c62ba8de..12fb25a36b 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -45,8 +45,6 @@ function getDockerImageCommitLabel() { local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" - dockerMakeCurl $URL "true" | jq . - DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` From e19eae2504d2a4cb2d08f996ef02b3915341fafe Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 17:03:27 +0100 Subject: [PATCH 23/34] Fix typo --- deploy/ci/build-images-stable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 26b9c99bd7..e1db43fd95 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -99,6 +99,6 @@ jobs: build: stratos dockerfile: stratos/deploy/Dockerfile.all-in-one tag: stratos/deploy/ci/tasks/build-images/stable-tag - tas_as_latest: true + tag_as_latest: true labels_file: image-tag/image-labels additional_tags: image-tag/v2-version From d5dccf134a964efa5c9af1499b6c765c0bfa94cc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 10:00:51 +0100 Subject: [PATCH 24/34] Add canary tag --- deploy/ci/tasks/build-images/canary-tag | 1 + 1 file changed, 1 insertion(+) create mode 100644 deploy/ci/tasks/build-images/canary-tag diff --git a/deploy/ci/tasks/build-images/canary-tag b/deploy/ci/tasks/build-images/canary-tag new file mode 100644 index 0000000000..be1bd41848 --- /dev/null +++ b/deploy/ci/tasks/build-images/canary-tag @@ -0,0 +1 @@ +canary From 7df2a64f3bf495a66b3234c4f1e398d719ae07c2 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 14:49:48 +0100 Subject: [PATCH 25/34] Re-enable Helm feature --- .../frontend/app/custom/custom.module.ts | 9 ++++----- src/jetstream/plugins/monocular/main.go | 18 +++++------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index cbcc12110c..e0dfea55d4 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -14,6 +14,8 @@ import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { KubeHealthCheck } from './kubernetes/store/kubernetes.actions'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; +import { HelmModule } from './helm/helm.module'; +import { HelmSetupModule } from './helm/helm.setup.module'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', @@ -27,11 +29,8 @@ const SuseCustomizations: CustomizationsMetadata = { SharedModule, MDAppModule, KubernetesSetupModule, - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // HelmModule, - // HelmSetupModule - // --------------------------------------- + HelmModule, + HelmSetupModule ], declarations: [ SuseLoginComponent, diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index a834164206..260c80ff9d 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -45,19 +45,11 @@ func (m *Monocular) GetChartStore() chartsvc.ChartSvcDatastore { // Init performs plugin initialization func (m *Monocular) Init() error { - return errors.New("Manually disabled") - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // m.ConfigureSQL() - - // m.chartSvcRoutes = chartsvc.GetRoutes() - - // m.InitSync() - - // m.syncOnStartup() - - // return nil - // --------------------------------------- + m.ConfigureSQL() + m.chartSvcRoutes = chartsvc.GetRoutes() + m.InitSync() + m.syncOnStartup() + return nil } func (m *Monocular) syncOnStartup() { From 6313b3d15f90036998e4f5db8c6c29374d935564 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 15:45:51 +0100 Subject: [PATCH 26/34] Add logging during customization --- build/customize-build.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/build/customize-build.js b/build/customize-build.js index 35cfaf5a3d..3c78d51476 100644 --- a/build/customize-build.js +++ b/build/customize-build.js @@ -26,6 +26,7 @@ doShowVersions() doCustomize(false); doGenerateIndexHtml(true); + console.log('Finished applying customizations') cb(); }); @@ -33,6 +34,7 @@ gulp.task('customize-default', function (cb) { doCustomize(true); doGenerateIndexHtml(false); + console.log('Finished applying default customizations') cb(); }); @@ -40,6 +42,7 @@ gulp.task('customize-reset', function (cb) { doCustomize(true, true); doGenerateIndexHtml(false); + console.log('Finished resetting customizations') cb(); }); @@ -112,6 +115,7 @@ if (!reset) { fs.symlinkSync(srcFile, destFile); + console.log(' + Linking file : ' + srcFile + ' ==> ' + destFile); } }) @@ -133,6 +137,7 @@ } if (!reset && fs.existsSync(srcFolder)) { fs.symlinkSync(srcFolder, destFolder); + console.log(' + Linking folder : ' + srcFolder + ' ==> ' + destFolder); } }); } @@ -140,13 +145,10 @@ // Copy the correct custom module to either import the supplied custom module or provide an empty module function doCustomizeCreateModule(forceDefaults, reset, customConfig, baseFolder, customBaseFolder) { const defaultSrcFolder = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom'); - console.log(baseFolder); const destFile = path.join(baseFolder, 'src/custom-import.module.ts'); const customModuleFile = path.join(baseFolder, 'src/custom/custom.module.ts'); const customRoutingModuleFile = path.join(baseFolder, 'src/custom/custom-routing.module.ts'); - console.log(customModuleFile); - // Delete the existing file if it exists if (fs.existsSync(destFile)) { fs.unlinkSync(destFile) @@ -158,9 +160,15 @@ srcFile = 'custom-src.module.ts_'; if (fs.existsSync(customRoutingModuleFile)) { srcFile = 'custom-src-routing.module.ts_'; + console.log(' + Found custom module with routing'); + } else { + console.log(' + Found custom module without routing'); } + } else { + console.log(' + No custom module found - linking empty custom module'); } fs.copySync(path.join(defaultSrcFolder, srcFile), destFile); + console.log(' + Copying file : ' + path.join(defaultSrcFolder, srcFile) + ' ==> ' + destFile); } } @@ -208,6 +216,8 @@ // Generate index.html from template function doGenerateIndexHtml(customize) { + + console.log(' + Generating index.html'); // Copy the default fs.copySync(INDEX_TEMPLATE, INDEX_HTML); @@ -223,6 +233,10 @@ } } + if (metadata.title) { + console.log(' + Overridding title to: "' + metadata.title + '"'); + } + // Patch different page title if there is one var title = metadata.title || 'Stratos'; replace.sync({ files: INDEX_HTML, from: /@@TITLE@@/g, to: title }); @@ -236,6 +250,7 @@ if (fs.existsSync(GIT_METADATA)) { gitMetadata = JSON.parse(fs.readFileSync(GIT_METADATA)); + console.log(" + Project Metadata: " + JSON.stringify(gitMetadata)); } // Git Information From 6663e2e951ef9ed75e5c53fada8cbf69dd7f50f4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 06:52:38 +0100 Subject: [PATCH 27/34] Fix typo --- .../kubernetes-gke-auth-form.component.html | 2 +- custom-src/frontend/assets/custom/help/en/connecting_gke.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html b/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html index ba7671b329..4d023829ec 100644 --- a/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html +++ b/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html @@ -1,6 +1,6 @@
    - Select an `Application Default Credentails' file: + Select an `Application Default Credentials' file: diff --git a/custom-src/frontend/assets/custom/help/en/connecting_gke.md b/custom-src/frontend/assets/custom/help/en/connecting_gke.md index dcdffdcf78..6940477366 100644 --- a/custom-src/frontend/assets/custom/help/en/connecting_gke.md +++ b/custom-src/frontend/assets/custom/help/en/connecting_gke.md @@ -18,4 +18,4 @@ This is the file that you should use when connecting in the endpoint connection > Note: You may need to copy this file to a non-hidden folder in order to be able to browse to it in the UI (or enable hidden files in your OS's file browser) -> Note: For more information on obtaining Application Default Credentails, refer to https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login \ No newline at end of file +> Note: For more information on obtaining Application Default Credentials, refer to https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login \ No newline at end of file From 807fc0c84ec5e4f9696d2c42cd5abacf5f00526f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 08:23:19 +0100 Subject: [PATCH 28/34] Docker AIO does not build with custom-src folder --- deploy/Dockerfile.all-in-one | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 5ee557343f..dbd4f0720e 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -1,12 +1,9 @@ # Docker build for all-in-one Stratos FROM splatform/stratos-aio-base:opensuse as builder -COPY --chown=stratos:users *.json ./ -COPY --chown=stratos:users gulpfile.js ./ -COPY --chown=stratos:users src ./src -COPY --chown=stratos:users build ./build/ +# Ensure that we copy the custom-src folder as well +COPY --chown=stratos:users . ./ COPY --chown=stratos:users deploy/tools/generate_cert.sh generate_cert.sh -COPY --chown=stratos:users deploy/db deploy/db COPY --chown=stratos:users deploy/all-in-one/config.all-in-one.properties config.properties RUN npm install \ From 295c38cd37664ba7f795ce74955386a6ae9d9ab4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 16 May 2019 16:43:54 +0100 Subject: [PATCH 29/34] Tidy up auth providers for Kubernetes, add OIDC support for Helm --- src/jetstream/oauth_requests.go | 4 +- src/jetstream/oauth_requests_test.go | 12 +- src/jetstream/passthrough.go | 2 +- .../{awsiam_requests.go => auth/awsiam.go} | 48 ++- .../plugins/kubernetes/auth/azure.go | 102 ++++++ .../{cert_requests.go => auth/cert.go} | 54 +-- .../kubernetes/{gke_auth.go => auth/gke.go} | 48 ++- .../plugins/kubernetes/auth/kubeconfig.go | 21 ++ src/jetstream/plugins/kubernetes/auth/oidc.go | 154 +++++++++ .../plugins/kubernetes/auth/types.go | 50 +++ .../plugins/kubernetes/auth_providers.go | 49 +++ .../plugins/kubernetes/config/kube_config.go | 154 +++++++++ .../plugins/kubernetes/endpoint_config.go | 64 +--- src/jetstream/plugins/kubernetes/go.mod | 2 +- src/jetstream/plugins/kubernetes/go.sum | 5 +- .../plugins/kubernetes/kube_config.go | 323 ------------------ .../plugins/kubernetes/kube_dashboard.go | 20 -- src/jetstream/plugins/kubernetes/main.go | 76 +---- .../repository/interfaces/portal_proxy.go | 5 +- 19 files changed, 682 insertions(+), 511 deletions(-) rename src/jetstream/plugins/kubernetes/{awsiam_requests.go => auth/awsiam.go} (68%) create mode 100644 src/jetstream/plugins/kubernetes/auth/azure.go rename src/jetstream/plugins/kubernetes/{cert_requests.go => auth/cert.go} (68%) rename src/jetstream/plugins/kubernetes/{gke_auth.go => auth/gke.go} (72%) create mode 100644 src/jetstream/plugins/kubernetes/auth/kubeconfig.go create mode 100644 src/jetstream/plugins/kubernetes/auth/oidc.go create mode 100644 src/jetstream/plugins/kubernetes/auth/types.go create mode 100644 src/jetstream/plugins/kubernetes/auth_providers.go create mode 100644 src/jetstream/plugins/kubernetes/config/kube_config.go delete mode 100644 src/jetstream/plugins/kubernetes/kube_config.go diff --git a/src/jetstream/oauth_requests.go b/src/jetstream/oauth_requests.go index f95970567a..b315e15a0e 100644 --- a/src/jetstream/oauth_requests.go +++ b/src/jetstream/oauth_requests.go @@ -47,8 +47,8 @@ func (p *portalProxy) OAuthHandlerFunc(cnsiRequest *interfaces.CNSIRequest, req } } -func (p *portalProxy) doOauthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { - log.Debug("doOauthFlowRequest") +func (p *portalProxy) DoOAuthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoOAuthFlowRequest") authHandler := p.OAuthHandlerFunc(cnsiRequest, req, p.RefreshOAuthToken) return p.DoAuthFlowRequest(cnsiRequest, req, authHandler) diff --git a/src/jetstream/oauth_requests_test.go b/src/jetstream/oauth_requests_test.go index 926cf00c9b..caacf39751 100644 --- a/src/jetstream/oauth_requests_test.go +++ b/src/jetstream/oauth_requests_test.go @@ -113,7 +113,7 @@ func TestDoOauthFlowRequestWithValidToken(t *testing.T) { WithArgs(mockCNSIGUID). WillReturnRows(expectedCNSIRecordRow) - res, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + res, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -255,7 +255,7 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) { WillReturnResult(sqlmock.NewResult(1, 1)) // - res, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + res, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -386,7 +386,7 @@ func TestDoOauthFlowRequestWithFailedRefreshMethod(t *testing.T) { WillReturnError(errors.New("Unknown Database Error")) // - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -430,7 +430,7 @@ func TestDoOauthFlowRequestWithMissingCNSITokenRecord(t *testing.T) { } pp.setCNSITokenRecord("not-the-right-guid", mockUserGUID, mockTokenRecord) - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -476,7 +476,7 @@ func TestDoOauthFlowRequestWithInvalidCNSIRequest(t *testing.T) { UserGUID: "", } - _, err := pp.doOauthFlowRequest(invalidCNSIRequest, req) + _, err := pp.DoOAuthFlowRequest(invalidCNSIRequest, req) Convey("Oauth flow request erroneously succeeded", func() { So(err, ShouldNotBeNil) @@ -642,7 +642,7 @@ func TestRefreshTokenWithDatabaseErrorOnSave(t *testing.T) { mock.ExpectExec(updateTokens). WillReturnError(errors.New("Unknown Database Error")) // - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index 604817f08d..604e034989 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -403,7 +403,7 @@ func (p *portalProxy) doRequest(cnsiRequest *interfaces.CNSIRequest, done chan<- if authHandler.Handler != nil { res, err = authHandler.Handler(cnsiRequest, req) } else { - res, err = p.doOauthFlowRequest(cnsiRequest, req) + res, err = p.DoOAuthFlowRequest(cnsiRequest, req) } if err != nil { diff --git a/src/jetstream/plugins/kubernetes/awsiam_requests.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go similarity index 68% rename from src/jetstream/plugins/kubernetes/awsiam_requests.go rename to src/jetstream/plugins/kubernetes/auth/awsiam.go index 58c96ade4e..9c8e950605 100644 --- a/src/jetstream/plugins/kubernetes/awsiam_requests.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -11,6 +11,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -25,6 +26,41 @@ type AWSIAMUserInfo struct { SecretKey string `json:"secretKey"` } +// AWSKubeAuth is AWS IAM Authentication for Kubernetes +type AWSKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +const AuthConnectTypeAWSIAM = "aws-iam" + +// InitAWSKubeAuth creates a GKEKubeAuth +func InitAWSKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &AWSKubeAuth{portalProxy: portalProxy} +} + +func (c *AWSKubeAuth) GetName() string { + return AuthConnectTypeAWSIAM +} + +func (c *AWSKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + awsInfo := &AWSIAMUserInfo{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) + if err != nil { + return err + } + + // NOTE: We really should check first to see if the token has expired before we try and get another + + // Get an access token + token, err := c.getTokenIAM(*awsInfo) + if err != nil { + return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + } + + info.Token = token + return nil +} + func (c *AWSIAMUserInfo) Retrieve() (credentials.Value, error) { return credentials.Value{ AccessKeyID: c.AccessKey, @@ -36,7 +72,7 @@ func (c *AWSIAMUserInfo) IsExpired() bool { return true } -func (c *KubernetesSpecification) FetchIAMToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { +func (c *AWSKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { log.Debug("FetchIAMToken") // Place the IAM properties into a JSON Struct and store that in the Refresh Token @@ -73,14 +109,14 @@ func (c *KubernetesSpecification) FetchIAMToken(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -func (c *KubernetesSpecification) GetCNSIUserFromIAMToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { +func (c *AWSKubeAuth) GetUserFromToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { return &interfaces.ConnectedUser{ GUID: "AWS IAM", Name: "IAM", }, true } -func (c *KubernetesSpecification) getTokenIAM(info AWSIAMUserInfo) (string, error) { +func (c *AWSKubeAuth) getTokenIAM(info AWSIAMUserInfo) (string, error) { generator, err := token.NewGenerator(false) if err != nil { return "", fmt.Errorf("AWS IAM: Failed to create generator due to %+v", err) @@ -105,14 +141,14 @@ func (c *KubernetesSpecification) getTokenIAM(info AWSIAMUserInfo) (string, erro return tok.Token, nil } -func (c *KubernetesSpecification) doAWSIAMFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *AWSKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doAWSIAMFlowRequest") authHandler := c.portalProxy.OAuthHandlerFunc(cnsiRequest, req, c.RefreshIAMToken) return c.portalProxy.DoAuthFlowRequest(cnsiRequest, req, authHandler) } -func (c *KubernetesSpecification) RefreshIAMToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *AWSKubeAuth) RefreshIAMToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshIAMToken") userToken, ok := c.portalProxy.GetCNSITokenRecordWithDisconnected(cnsiGUID, userGUID) diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go new file mode 100644 index 0000000000..4f0f0ab41b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -0,0 +1,102 @@ +package auth + +import ( + "encoding/base64" + "errors" + "io/ioutil" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + + "github.com/labstack/echo" +) + +const AuthConnectTypeKubeConfigAz = "kubeconfig-az" + + +// AzureKubeAuth is Azure Authentication with Certificates +type AzureKubeAuth struct { + CertKubeAuth +} + +// InitAzureKubeAuth creates a AzureKubeAuth +func InitAzureKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &AzureKubeAuth{*InitCertKubeAuth(portalProxy)} +} + +// GetName returns the provider name +func (c *AzureKubeAuth) GetName() string { + return AuthConnectTypeKubeConfigAz +} + +func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + req := ec.Request() + + // Need to extract the parameters from the request body + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, nil, err + } + + kubeConfig, err := config.ParseKubeConfig(body) + + kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) + if err != nil { + return nil, nil, errors.New("Unable to find cluster in kubeconfig") + } + + authConfig, err := p.getAKSAuthConfig(kubeConfigUser) + if err != nil { + return nil, nil, errors.New("User doesn't use AKS auth") + } + + jsonString, err := authConfig.GetJSON() + if err != nil { + return nil, nil, err + } + // Refresh token isn't required since the AccessToken will never expire + refreshToken := jsonString + + accessToken := jsonString + // Indefinite expiry + expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) + + tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) + tokenRecord.AuthType = AuthConnectTypeKubeConfigAz + + return &tokenRecord, &cnsiRecord, nil +} + + +func (p *AzureKubeAuth) getAKSAuthConfig(k *config.KubeConfigUser) (*KubeCertificate, error) { + + if !isAKSAuth(k) { + return nil, errors.New("User doesn't use AKS") + } + + cert, err := base64.StdEncoding.DecodeString(k.User.ClientCertificate) + if err != nil { + return nil, errors.New("Unable to decode certificate") + } + certKey, err := base64.StdEncoding.DecodeString(k.User.ClientKeyData) + if err != nil { + return nil, errors.New("Unable to decode certificate key") + } + kubeCertAuth := &KubeCertificate{ + Certificate: string(cert), + CertificateKey: string(certKey), + Token: k.User.Token, + } + return kubeCertAuth, nil +} + +func isAKSAuth(k *config.KubeConfigUser) bool { + if k.User.ClientCertificate == "" || + k.User.ClientKeyData == "" || + k.User.Token == "" { + return false + } + return true +} diff --git a/src/jetstream/plugins/kubernetes/cert_requests.go b/src/jetstream/plugins/kubernetes/auth/cert.go similarity index 68% rename from src/jetstream/plugins/kubernetes/cert_requests.go rename to src/jetstream/plugins/kubernetes/auth/cert.go index de552389ea..9e7569587b 100644 --- a/src/jetstream/plugins/kubernetes/cert_requests.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "bytes" @@ -16,33 +16,43 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -type KubeCertAuth struct { - Certificate string `json:"cert"` - CertificateKey string `json:"certKey"` - Token string `json:"token,omitempty"` +const AuthConnectTypeCertAuth = "kube-cert-auth" + + +// CertKubeAuth is GKE Authentication with Certificates +type CertKubeAuth struct { + portalProxy interfaces.PortalProxy } -func (k *KubeCertAuth) GetJSON() (string, error) { - jsonString, err := json.Marshal(k) - if err != nil { - return "", err - } - return string(jsonString), nil +// InitCertKubeAuth creates a GKEKubeAuth +func InitCertKubeAuth(portalProxy interfaces.PortalProxy) *CertKubeAuth { + return &CertKubeAuth{portalProxy: portalProxy} } -func (k *KubeCertAuth) GetCerticate() (tls.Certificate, error) { - cert, err := tls.X509KeyPair([]byte(k.Certificate), []byte(k.CertificateKey)) +func (c *CertKubeAuth) GetName() string { + return AuthConnectTypeCertAuth +} + +func (c *CertKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + kubeAuthToken := &KubeCertificate{} + err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { - return tls.Certificate{}, err + return err } - return cert, nil + + info.ClientCertificateData = []byte(kubeAuthToken.Certificate) + info.ClientKeyData = []byte(kubeAuthToken.CertificateKey) + info.Token = kubeAuthToken.Token + + return nil } -func (c *KubernetesSpecification) extractCerts(ec echo.Context) (*KubeCertAuth, error) { +func (c *CertKubeAuth) extractCerts(ec echo.Context) (*KubeCertificate, error) { - kubeCertAuth := &KubeCertAuth{} + kubeCertAuth := &KubeCertificate{} bodyReader := ec.Request().Body defer bodyReader.Close() @@ -66,7 +76,7 @@ func (c *KubernetesSpecification) extractCerts(ec echo.Context) (*KubeCertAuth, } -func (c *KubernetesSpecification) FetchCertAuth(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { +func (c *CertKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { log.Info("FetchCerts") kubeCertAuth, err := c.extractCerts(ec) @@ -92,19 +102,19 @@ func (c *KubernetesSpecification) FetchCertAuth(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -func (c *KubernetesSpecification) GetCNSIUserFromCertAuth(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { +func (c *CertKubeAuth) GetUserFromToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { return &interfaces.ConnectedUser{ GUID: "Kube Cert Auth", Name: "Cert Auth", }, true } -func (c *KubernetesSpecification) doCertAuthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *CertKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doCertAuthFlowRequest") authHandler := func(tokenRec interfaces.TokenRecord, cnsi interfaces.CNSIRecord) (*http.Response, error) { - kubeAuthToken := &KubeCertAuth{} + kubeAuthToken := &KubeCertificate{} err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { return nil, err @@ -154,7 +164,7 @@ func (c *KubernetesSpecification) doCertAuthFlowRequest(cnsiRequest *interfaces. return c.portalProxy.DoAuthFlowRequest(cnsiRequest, req, authHandler) } -func (c *KubernetesSpecification) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *CertKubeAuth) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshCertAuth") // This shouldn't be called since cert-auth K8S shouldn't expire diff --git a/src/jetstream/plugins/kubernetes/gke_auth.go b/src/jetstream/plugins/kubernetes/auth/gke.go similarity index 72% rename from src/jetstream/plugins/kubernetes/gke_auth.go rename to src/jetstream/plugins/kubernetes/auth/gke.go index c25d8631da..3bdeced3b4 100644 --- a/src/jetstream/plugins/kubernetes/gke_auth.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -13,6 +13,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/SermoDigital/jose/jws" ) @@ -31,9 +32,36 @@ type GKEConfig struct { Email string `json:"email"` } -// FetchGKEToken will create a token for the GKE Authentication using the POSTed data -func (c *KubernetesSpecification) FetchGKEToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - log.Debug("FetchGKEToken") +// GKEKubeAuth is GKE Authentication for Kubernetes +type GKEKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +const AuthConnectTypeGKE = "gke-auth" + +// InitGKEKubeAuth creates a GKEKubeAuth +func InitGKEKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &GKEKubeAuth{portalProxy: portalProxy} +} + +func (c *GKEKubeAuth) GetName() string { + return AuthConnectTypeGKE +} + +func (c *GKEKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + gkeInfo := &GKEConfig{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) + if err != nil { + return err + } + + info.Token = tokenRec.AuthToken + return nil +} + +// FetchToken will create a token for the GKE Authentication using the POSTed data +func (c *GKEKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (GKE)") // We should already have the refresh token in the body sent to us req := ec.Request() @@ -83,9 +111,9 @@ func (c *KubernetesSpecification) FetchGKEToken(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -// GetGKEUserFromToken gets the username from the GKE Token -func (c *KubernetesSpecification) GetGKEUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { - log.Debug("GetGKEUserFromToken") +// GetUserFromToken gets the username from the GKE Token +func (c *GKEKubeAuth) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { + log.Debug("GetUserFromToken (GKE)") gkeInfo := &GKEConfig{} err := json.Unmarshal([]byte(tokenRecord.RefreshToken), &gkeInfo) @@ -99,7 +127,7 @@ func (c *KubernetesSpecification) GetGKEUserFromToken(cnsiGUID string, tokenReco }, true } -func (c *KubernetesSpecification) doGKEFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *GKEKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doGKEFlowRequest") authHandler := c.portalProxy.OAuthHandlerFunc(cnsiRequest, req, c.RefreshGKEToken) @@ -107,7 +135,7 @@ func (c *KubernetesSpecification) doGKEFlowRequest(cnsiRequest *interfaces.CNSIR } // RefreshGKEToken will refresh a GKE token -func (c *KubernetesSpecification) RefreshGKEToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *GKEKubeAuth) RefreshGKEToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshGKEToken") now := time.Now() @@ -137,7 +165,7 @@ func (c *KubernetesSpecification) RefreshGKEToken(skipSSLValidation bool, cnsiGU return userToken, nil } -func (c *KubernetesSpecification) refreshGKEToken(skipSSLValidation bool, clientID, clientSecret, refreshToken string) (u interfaces.UAAResponse, err error) { +func (c *GKEKubeAuth) refreshGKEToken(skipSSLValidation bool, clientID, clientSecret, refreshToken string) (u interfaces.UAAResponse, err error) { log.Debug("refreshGKEToken") tokenInfo := interfaces.UAAResponse{} diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go new file mode 100644 index 0000000000..085a9d82ac --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -0,0 +1,21 @@ +package auth + +import ( + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +const AuthConnectTypeKubeConfig = "KubeConfig" + +// KubeConfigAuth is same as OIDC with different name +type KubeConfigAuth struct { + OIDCKubeAuth +} + +// InitKubeConfigAuth +func InitKubeConfigAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &KubeConfigAuth{*InitOIDCKubeAuth(portalProxy)} +} + +func (c *KubeConfigAuth) GetName() string { + return AuthConnectTypeKubeConfig +} diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go new file mode 100644 index 0000000000..80ce00dda0 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -0,0 +1,154 @@ +package auth + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "io/ioutil" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + + log "github.com/sirupsen/logrus" + "github.com/labstack/echo" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "github.com/SermoDigital/jose/jws" +) + +type KubeConfigAuthProviderOIDC struct { + ClientID string `yaml:"client-id"` + ClientSecret string `yaml:"client-secret"` + IDToken string `yaml:"id-token"` + IdpIssuerURL string `yaml:"idp-issuer-url"` + RefreshToken string `yaml:"refresh-token"` + Expiry time.Time +} + +const AuthConnectTypeOIDC = "OIDC" + +// OIDCKubeAuth +type OIDCKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +// InitOIDCKubeAuth +func InitOIDCKubeAuth(portalProxy interfaces.PortalProxy) *OIDCKubeAuth { + return &OIDCKubeAuth{portalProxy: portalProxy} +} + +func (c *OIDCKubeAuth) GetName() string { + return AuthConnectTypeOIDC +} + +func (c *OIDCKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + log.Info("AddAuthInfo") + + authInfo := &interfaces.OAuth2Metadata{} + err := json.Unmarshal([]byte(tokenRec.Metadata), &authInfo) + if err != nil { + return err + } + + info.AuthProvider = &clientcmdapi.AuthProviderConfig{} + info.AuthProvider.Name = "oidc" + info.AuthProvider.Config = make(map[string]string) + info.AuthProvider.Config["client-id"] = authInfo.ClientID + info.AuthProvider.Config["client-secret"] = authInfo.ClientSecret + info.AuthProvider.Config["idp-issuer-url"] = authInfo.IssuerURL + + info.AuthProvider.Config["id-token"] = tokenRec.AuthToken + info.AuthProvider.Config["refresh-token"] = tokenRec.RefreshToken + info.AuthProvider.Config["extra-scopes"] = "groups" + + return nil +} + +func (c *OIDCKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (OIDC)") + + req := ec.Request() + + // Need to extract the parameters from the request body + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, nil, err + } + + kubeConfig, err := config.ParseKubeConfig(body) + + kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) + + if err != nil { + return nil, nil, fmt.Errorf("Unable to find cluster in kubeconfig") + } + + // We only support OIDC auth provider at the moment + if kubeConfigUser.User.AuthProvider.Name != "oidc" { + return nil, nil, errors.New("Unsupported authentication provider") + } + + oidcConfig, err := c.GetOIDCConfig(kubeConfigUser) + if err != nil { + log.Info(err) + return nil, nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") + } + tokenRecord := c.portalProxy.InitEndpointTokenRecord(oidcConfig.Expiry.Unix(), oidcConfig.IDToken, oidcConfig.RefreshToken, false) + tokenRecord.AuthType = interfaces.AuthTypeOIDC + + oauthMetadata := &interfaces.OAuth2Metadata{} + oauthMetadata.ClientID = oidcConfig.ClientID + oauthMetadata.ClientSecret = oidcConfig.ClientSecret + oauthMetadata.IssuerURL = oidcConfig.IdpIssuerURL + + jsonString, err := json.Marshal(oauthMetadata) + if err == nil { + tokenRecord.Metadata = string(jsonString) + } + + // Could try and make a K8S Api call to validate the token + // Or, maybe we can verify the access token with the auth URL ? + + return &tokenRecord, &cnsiRecord, nil +} + +// GetUserFromToken gets the username from the GKE Token +func (c *OIDCKubeAuth) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { + log.Debug("GetUserFromToken (OIDC)") + return c.portalProxy.GetCNSIUserFromOAuthToken(cnsiGUID, tokenRecord) +} + +func (c *OIDCKubeAuth) GetOIDCConfig(k *config.KubeConfigUser) (*KubeConfigAuthProviderOIDC, error) { + + if k.User.AuthProvider.Name != "oidc" { + return nil, errors.New("User doesn't use OIDC") + } + + OIDCConfig := &KubeConfigAuthProviderOIDC{} + err := config.UnMarshalHelper(k.User.AuthProvider.Config, OIDCConfig) + if err != nil { + log.Info(err) + return nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") + } + + token, err := jws.ParseJWT([]byte(OIDCConfig.IDToken)) + if err != nil { + log.Info(err) + return nil, errors.New("Can not parse JWT Access token") + } + + expiry, ok := token.Claims().Expiration() + if !ok { + return nil, errors.New("Can not get Acces Token expiry time") + } + OIDCConfig.Expiry = expiry + + return OIDCConfig, nil +} + +func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoFlowRequest (OIDC)") + return c.portalProxy.DoOAuthFlowRequest(cnsiRequest, req) +} diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go new file mode 100644 index 0000000000..127ac705b1 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -0,0 +1,50 @@ +package auth + +import ( + "crypto/tls" + "encoding/json" + "net/http" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + + "github.com/labstack/echo" +) + +// KubeAuthProvider is the interface for Kubernetes Authentication Providers +type KubeAuthProvider interface { + GetName() string + AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error + FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) + + DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) + GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) +} + + +// ------------------------------- + +// KubeCertificate represents certificate infor for Kube Authentication +type KubeCertificate struct { + Certificate string `json:"cert"` + CertificateKey string `json:"certKey"` + Token string `json:"token,omitempty"` +} + +// GetJSON persists the config to JSON +func (k *KubeCertificate) GetJSON() (string, error) { + jsonString, err := json.Marshal(k) + if err != nil { + return "", err + } + return string(jsonString), nil +} + +// GetCerticate gets a certiciate from the info available +func (k *KubeCertificate) GetCerticate() (tls.Certificate, error) { + cert, err := tls.X509KeyPair([]byte(k.Certificate), []byte(k.CertificateKey)) + if err != nil { + return tls.Certificate{}, err + } + return cert, nil +} \ No newline at end of file diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go new file mode 100644 index 0000000000..c078f0f61a --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -0,0 +1,49 @@ +package kubernetes + +import ( + "strings" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +// Interface for Kubernetes Authentication Providers + +var kubeAuthProviders map[string]auth.KubeAuthProvider + +// AddAuthProvider adds a Kubernetes auth provider +func (c *KubernetesSpecification) AddAuthProvider(provider auth.KubeAuthProvider) { + if provider == nil { + return + } + + var name = provider.GetName() + if kubeAuthProviders == nil { + kubeAuthProviders = make(map[string]auth.KubeAuthProvider) + } + + kubeAuthProviders[name] = provider + + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(name, interfaces.AuthProvider{ + Handler: provider.DoFlowRequest, + UserInfo: provider.GetUserFromToken, + }) + +} + +// GetAuthProvider gets a Kubernetes auth provider by key +func (c *KubernetesSpecification) GetAuthProvider(name string) auth.KubeAuthProvider { + return kubeAuthProviders[name] +} + +// FindAuthProvider finds auth provider - case insensitive +func (c *KubernetesSpecification) FindAuthProvider(name string) auth.KubeAuthProvider { + for k, v := range kubeAuthProviders { + if strings.EqualFold(name, k) { + return v + } + } + + return nil +} diff --git a/src/jetstream/plugins/kubernetes/config/kube_config.go b/src/jetstream/plugins/kubernetes/config/kube_config.go new file mode 100644 index 0000000000..3a9ab7419b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/config/kube_config.go @@ -0,0 +1,154 @@ +package config + +import ( + "errors" + "fmt" + "reflect" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" + + "gopkg.in/yaml.v2" + +) + +type KubeConfigClusterDetail struct { + Server string `yaml:"server"` +} + +type KubeConfigCluster struct { + Name string `yaml:"name"` + Cluster struct { + Server string + } +} + +type KubeConfigUser struct { + Name string `yaml:"name"` + User struct { + AuthProvider struct { + Name string `yaml:"name"` + Config map[string]interface{} `yaml:"config"` + } `yaml:"auth-provider,omitempty"` + ClientCertificate string `yaml:"client-certificate-data,omitempty"` + ClientKeyData string `yaml:"client-key-data,omitempty"` + Token string `yaml:"token,omitempty"` + } +} + +//ExtraScopes string `yaml:"extra-scopes"` + +type KubeConfigContexts struct { + Context struct { + Cluster string + User string + } `yaml:"context"` +} + +type KubeConfigFile struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Clusters []KubeConfigCluster `yaml:"clusters"` + Users []KubeConfigUser `yaml:"users"` + Contexts []KubeConfigContexts `yaml:"contexts"` +} + +func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCluster, error) { + for _, cluster := range k.Clusters { + if cluster.Cluster.Server == endpoint { + return &cluster, nil + } + } + return nil, fmt.Errorf("Unable to find cluster") +} + +func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigContexts, error) { + for _, context := range k.Contexts { + if context.Context.Cluster == clusterName { + return &context, nil + } + } + return nil, fmt.Errorf("Unable to find context") +} + +func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { + for _, user := range k.Users { + if user.Name == userName { + return &user, nil + } + } + return nil, fmt.Errorf("Unable to find user") +} + +func (k *KubeConfigFile) GetUserForCluster(clusterEndpoint string) (*KubeConfigUser, error) { + cluster, err := k.GetClusterByAPIEndpoint(clusterEndpoint) + + if err != nil { + return nil, errors.New("Unable to find cluster in kubeconfig") + } + + clusterName := cluster.Name + + if clusterName == "" { + return nil, errors.New("Unable to find cluster") + } + + context, err := k.GetClusterContext(clusterName) + if err != nil { + return nil, errors.New("Unable to find cluster context") + } + + kubeConfigUser, err := k.GetUser(context.Context.User) + if err != nil { + return nil, errors.New("Can not find config for Kubernetes cluster") + } + + return kubeConfigUser, nil +} + +func ParseKubeConfig(kubeConfigData []byte) (*KubeConfigFile, error) { + + kubeConfig := &KubeConfigFile{} + err := yaml.Unmarshal(kubeConfigData, &kubeConfig) + if err != nil { + return nil, err + } + if kubeConfig.ApiVersion != "v1" || kubeConfig.Kind != "Config" { + return nil, errors.New("Not a valid Kubernetes Config file") + } + + return kubeConfig, nil +} + +func UnMarshalHelper(values map[string]interface{}, intf interface{}) error { + + value := reflect.ValueOf(intf) + + if value.Kind() != reflect.Ptr { + return errors.New("config: must provide pointer to struct value") + } + + value = value.Elem() + if value.Kind() != reflect.Struct { + return errors.New("config: must provide pointer to struct value") + } + + nFields := value.NumField() + typ := value.Type() + + for i := 0; i < nFields; i++ { + field := value.Field(i) + strField := typ.Field(i) + tag := strField.Tag.Get("yaml") + if tag == "" { + continue + } + + if tagValue, ok := values[tag].(string); ok { + if err := config.SetStructFieldValue(value, field, tagValue); err != nil { + return err + } + } + } + + return nil +} diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index 06893ebe52..f047d86898 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -1,10 +1,7 @@ package kubernetes import ( - "encoding/json" "errors" - "fmt" - "strings" log "github.com/sirupsen/logrus" @@ -19,7 +16,7 @@ import ( func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { - log.Debug("GetConfigForEndpoint") + log.Info("GetConfigForEndpoint") name := "cluster-0" @@ -55,61 +52,10 @@ func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.Auth log.Debug("addAuthInfoForEndpoint") log.Warn(tokenRec.AuthType) - switch { - case tokenRec.AuthType == "gke-auth": - log.Warn("GKE AUTH") - return c.addGKEAuth(info, tokenRec) - case tokenRec.AuthType == AuthConnectTypeCertAuth, tokenRec.AuthType == AuthConnectTypeKubeConfigAz: - return c.addCertAuth(info, tokenRec) - case tokenRec.AuthType == AuthConnectTypeAWSIAM: - return c.addAWSAuth(info, tokenRec) - default: - log.Error("Unsupported auth type") - } - return errors.New("Unsupported auth type") -} - -func (c *KubernetesSpecification) addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - kubeAuthToken := &KubeCertAuth{} - err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) - if err != nil { - return err - } - - info.ClientCertificateData = []byte(kubeAuthToken.Certificate) - info.ClientKeyData = []byte(kubeAuthToken.CertificateKey) - info.Token = kubeAuthToken.Token - - return nil -} - -func (c *KubernetesSpecification) addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - gkeInfo := &GKEConfig{} - err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) - if err != nil { - return err - } - - info.Token = tokenRec.AuthToken - return nil -} - -func (c *KubernetesSpecification) addAWSAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - - awsInfo := &AWSIAMUserInfo{} - err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) - if err != nil { - return err - } - - // NOTE: We really should check first to see if the token has expired before we try and get another - - // Get an access token - token, err := c.getTokenIAM(*awsInfo) - if err != nil { - return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + var authProvider = c.GetAuthProvider(tokenRec.AuthType) + if authProvider == nil { + return errors.New("Unsupported auth type") } - info.Token = token - return nil + return authProvider.AddAuthInfo(info, tokenRec) } diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index 172fbd9788..b9056ff45a 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -1,4 +1,4 @@ -module github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetetes +module github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes go 1.12 diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index 71244c83a1..836c47b4bc 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -27,6 +27,8 @@ github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry-community/go-cfenv v1.17.0 h1:qfxEfn8qKkaHY3ZEk/Y2noY79HBASvNgmtHK9x4+6GY= github.com/cloudfoundry-community/go-cfenv v1.17.0/go.mod h1:2UgWvQTRXUuIZ/x3KnW6fk6CgPBhcV4UQb/UGIrUyyI= +github.com/cloudfoundry-incubator/stratos v2.0.0-beta-001+incompatible h1:UUxNbLjhv2cfymub5yNN1tjjqYkteHBBagb4jcbXEIQ= +github.com/cloudfoundry-incubator/stratos/src/jetstream v0.0.0-20190516104506-727fa3589a90 h1:IeIyBIgh2xEQm1CxHN2yFSBU7Ap+HQZleOs3TlAymI0= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -91,6 +93,7 @@ github.com/helm/monocular v1.4.0 h1:g0sOpuMe+9u+aPfd9ZO8mWV+c8W0dfGyBG9Wl23nwec= github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z33a9/gE= github.com/helm/monocular v1.5.0 h1:y8anOb2XLsCluYNOPx01G6EPqyBZ9fxDi+9BKbesULE= github.com/helm/monocular v1.6.0 h1:zE8OggduPEtnF2x1XgrQnMwnKFUGCvk/RNs0NX8hgjg= +github.com/helm/monocular v1.7.0 h1:SW2xr6KoVJtZT6ZAtON8jJVLqcRE3C8pBb9kyo8Icxw= github.com/heptio/authenticator v0.3.0 h1:Xh6XWkLZ+CksGuky+vsr77mHnI9C4L3nwj+xuZu28J0= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= @@ -151,9 +154,9 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= -github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= diff --git a/src/jetstream/plugins/kubernetes/kube_config.go b/src/jetstream/plugins/kubernetes/kube_config.go deleted file mode 100644 index 8d5c530883..0000000000 --- a/src/jetstream/plugins/kubernetes/kube_config.go +++ /dev/null @@ -1,323 +0,0 @@ -package kubernetes - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "reflect" - "time" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" - "github.com/labstack/echo" - log "github.com/sirupsen/logrus" - - "gopkg.in/yaml.v2" - - "github.com/SermoDigital/jose/jws" -) - -type KubeConfigClusterDetail struct { - Server string `yaml:"server"` -} - -type KubeConfigCluster struct { - Name string `yaml:"name"` - Cluster struct { - Server string - } -} - -type KubeConfigAuthProviderOIDC struct { - ClientID string `yaml:"client-id"` - ClientSecret string `yaml:"client-secret"` - IDToken string `yaml:"id-token"` - IdpIssuerURL string `yaml:"idp-issuer-url"` - RefreshToken string `yaml:"refresh-token"` - Expiry time.Time -} - -type KubeConfigUser struct { - Name string `yaml:"name"` - User struct { - AuthProvider struct { - Name string `yaml:"name"` - Config map[string]interface{} `yaml:"config"` - } `yaml:"auth-provider,omitempty"` - ClientCertificate string `yaml:"client-certificate-data,omitempty"` - ClientKeyData string `yaml:"client-key-data,omitempty"` - Token string `yaml:"token,omitempty"` - } -} - -func (k *KubeConfigUser) isOIDCAuth() bool { - if k.User.AuthProvider.Name != "oidc" { - return false - } - return true -} -func (k *KubeConfigUser) isAKSAuth() bool { - if k.User.ClientCertificate == "" || - k.User.ClientKeyData == "" || - k.User.Token == "" { - return false - } - return true -} - -func (k *KubeConfigUser) getOIDCConfig() (*KubeConfigAuthProviderOIDC, error) { - - if !k.isOIDCAuth() { - return nil, errors.New("User doesn't use OIDC") - } - OIDCConfig := &KubeConfigAuthProviderOIDC{} - err := unMarshalHelper(k.User.AuthProvider.Config, OIDCConfig) - if err != nil { - log.Info(err) - return nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") - } - - token, err := jws.ParseJWT([]byte(OIDCConfig.IDToken)) - if err != nil { - log.Info(err) - return nil, errors.New("Can not parse JWT Access token") - } - - expiry, ok := token.Claims().Expiration() - if !ok { - return nil, errors.New("Can not get Acces Token expiry time") - } - OIDCConfig.Expiry = expiry - - return OIDCConfig, nil -} - -func (k *KubeConfigUser) getAKSAuthConfig() (*KubeCertAuth, error) { - - if !k.isAKSAuth() { - return nil, errors.New("User doesn't use AKS") - } - - cert, err := base64.StdEncoding.DecodeString(k.User.ClientCertificate) - if err != nil { - return nil, errors.New("Unable to decode certificate") - } - certKey, err := base64.StdEncoding.DecodeString(k.User.ClientKeyData) - if err != nil { - return nil, errors.New("Unable to decode certificate key") - } - kubeCertAuth := &KubeCertAuth{ - Certificate: string(cert), - CertificateKey: string(certKey), - Token: k.User.Token, - } - return kubeCertAuth, nil -} - -//ExtraScopes string `yaml:"extra-scopes"` - -type KubeConfigContexts struct { - Context struct { - Cluster string - User string - } `yaml:"context"` -} - -type KubeConfigFile struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Clusters []KubeConfigCluster `yaml:"clusters"` - Users []KubeConfigUser `yaml:"users"` - Contexts []KubeConfigContexts `yaml:"contexts"` -} - -func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCluster, error) { - for _, cluster := range k.Clusters { - if cluster.Cluster.Server == endpoint { - return &cluster, nil - } - } - return nil, fmt.Errorf("Unable to find cluster") -} - -func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigContexts, error) { - for _, context := range k.Contexts { - if context.Context.Cluster == clusterName { - return &context, nil - } - } - return nil, fmt.Errorf("Unable to find context") -} - -func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { - for _, user := range k.Users { - if user.Name == userName { - return &user, nil - } - } - return nil, fmt.Errorf("Unable to find user") -} - -func (k *KubeConfigFile) GetUserForCluster(clusterEndpoint string) (*KubeConfigUser, error) { - cluster, err := k.GetClusterByAPIEndpoint(clusterEndpoint) - - if err != nil { - return nil, errors.New("Unable to find cluster in kubeconfig") - } - - clusterName := cluster.Name - - if clusterName == "" { - return nil, errors.New("Unable to find cluster") - } - - context, err := k.GetClusterContext(clusterName) - if err != nil { - return nil, errors.New("Unable to find cluster context") - } - - kubeConfigUser, err := k.GetUser(context.Context.User) - if err != nil { - return nil, errors.New("Can not find config for Kubernetes cluster") - } - - return kubeConfigUser, nil -} - -func (p *KubernetesSpecification) parseKubeConfig(kubeConfigData []byte) (*KubeConfigFile, error) { - - kubeConfig := &KubeConfigFile{} - err := yaml.Unmarshal(kubeConfigData, &kubeConfig) - if err != nil { - return nil, err - } - if kubeConfig.ApiVersion != "v1" || kubeConfig.Kind != "Config" { - return nil, errors.New("Not a valid Kubernetes Config file") - } - - return kubeConfig, nil -} - -func (p *KubernetesSpecification) FetchKubeConfigTokenOIDC(cnsiRecord interfaces.CNSIRecord, c echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - - req := c.Request() - - // Need to extract the parameters from the request body - defer req.Body.Close() - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, nil, err - } - - kubeConfig, err := p.parseKubeConfig(body) - - kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) - - if err != nil { - return nil, nil, fmt.Errorf("Unable to find cluster in kubeconfig") - } - - // We only support OIDC auth provider at the moment - if kubeConfigUser.User.AuthProvider.Name != "oidc" { - return nil, nil, errors.New("Unsupported authentication provider") - } - - oidcConfig, err := kubeConfigUser.getOIDCConfig() - if err != nil { - log.Info(err) - return nil, nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") - } - tokenRecord := p.portalProxy.InitEndpointTokenRecord(oidcConfig.Expiry.Unix(), oidcConfig.IDToken, oidcConfig.RefreshToken, false) - tokenRecord.AuthType = interfaces.AuthTypeOIDC - - oauthMetadata := &interfaces.OAuth2Metadata{} - oauthMetadata.ClientID = oidcConfig.ClientID - oauthMetadata.ClientSecret = oidcConfig.ClientSecret - oauthMetadata.IssuerURL = oidcConfig.IdpIssuerURL - - jsonString, err := json.Marshal(oauthMetadata) - if err == nil { - tokenRecord.Metadata = string(jsonString) - } - - // Could try and make a K8S Api call to validate the token - // Or, maybe we can verify the access token with the auth URL ? - - return &tokenRecord, &cnsiRecord, nil -} - -func (p *KubernetesSpecification) FetchKubeConfigTokenAKS(cnsiRecord interfaces.CNSIRecord, c echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - - req := c.Request() - - // Need to extract the parameters from the request body - defer req.Body.Close() - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, nil, err - } - - kubeConfig, err := p.parseKubeConfig(body) - - kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) - if err != nil { - return nil, nil, errors.New("Unable to find cluster in kubeconfig") - } - - authConfig, err := kubeConfigUser.getAKSAuthConfig() - if err != nil { - return nil, nil, errors.New("User doesn't use AKS auth") - } - - jsonString, err := authConfig.GetJSON() - if err != nil { - return nil, nil, err - } - // Refresh token isn't required since the AccessToken will never expire - refreshToken := jsonString - - accessToken := jsonString - // Indefinite expiry - expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) - - tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeKubeConfigAz - - return &tokenRecord, &cnsiRecord, nil -} - -func unMarshalHelper(values map[string]interface{}, intf interface{}) error { - - value := reflect.ValueOf(intf) - - if value.Kind() != reflect.Ptr { - return errors.New("config: must provide pointer to struct value") - } - - value = value.Elem() - if value.Kind() != reflect.Struct { - return errors.New("config: must provide pointer to struct value") - } - - nFields := value.NumField() - typ := value.Type() - - for i := 0; i < nFields; i++ { - field := value.Field(i) - strField := typ.Field(i) - tag := strField.Tag.Get("yaml") - if tag == "" { - continue - } - - if tagValue, ok := values[tag].(string); ok { - if err := config.SetStructFieldValue(value, field, tagValue); err != nil { - return err - } - } - } - - return nil -} diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index afcbb0b078..f2d37a4b65 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -56,26 +56,6 @@ func (k *KubernetesSpecification) getConfig(cnsiRecord *interfaces.CNSIRecord, t return k.GetConfigForEndpoint(masterURL, *tokenRecord) } -// Get the config for the certificate authentication -func __getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { - - config := rest.Config{} - config.Host = cnsiRecord.APIEndpoint.String() - - // Only support certs for now - kubeAuthToken := &KubeCertAuth{} - err := json.NewDecoder(strings.NewReader(tokenRecord.AuthToken)).Decode(kubeAuthToken) - if err != nil { - return nil, err - } - - config.TLSClientConfig = rest.TLSClientConfig{} - config.TLSClientConfig.CertData = []byte(kubeAuthToken.Certificate) - config.TLSClientConfig.KeyData = []byte(kubeAuthToken.CertificateKey) - config.TLSClientConfig.Insecure = true - return &config, nil -} - // makeUpgradeTransport creates a transport that explicitly bypasses HTTP2 support // for proxy connections that must upgrade. func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (proxy.UpgradeRequestRoundTripper, error) { diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index b8b0598ed5..26a36b433d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -2,13 +2,14 @@ package kubernetes import ( "net/url" - "strings" "errors" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" ) type KubernetesSpecification struct { @@ -76,73 +77,30 @@ func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces connectType := ec.FormValue("connect_type") - // OIDC ? - if strings.EqualFold(connectType, AuthConnectTypeKubeConfig) { - tokenRecord, _, err := c.FetchKubeConfigTokenOIDC(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // AKS ? - if strings.EqualFold(connectType, AuthConnectTypeKubeConfigAz) { - tokenRecord, _, err := c.FetchKubeConfigTokenAKS(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // IAM Creds? - if strings.EqualFold(connectType, AuthConnectTypeAWSIAM) { - tokenRecord, _, err := c.FetchIAMToken(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // Cert Auth? - if strings.EqualFold(connectType, AuthConnectTypeCertAuth) { - tokenRecord, _, err := c.FetchCertAuth(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil + var authProvider = c.FindAuthProvider(connectType) + if authProvider == nil { + return nil, false, errors.New("Unsupported Auth connection type for Kubernetes endpoint") } - // GKE ? - if strings.EqualFold(connectType, AuthConnectTypeGKE) { - tokenRecord, _, err := c.FetchGKEToken(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil + tokenRecord, _, err := authProvider.FetchToken(cnsiRecord, ec) + if err != nil { + return nil, false, err } - return nil, false, errors.New("Unsupported Auth connection type for Kubernetes endpoint") + return tokenRecord, false, nil } // Init the Kubernetes Jetstream plugin func (c *KubernetesSpecification) Init() error { - c.portalProxy.AddAuthProvider(AuthConnectTypeAWSIAM, interfaces.AuthProvider{ - Handler: c.doAWSIAMFlowRequest, - UserInfo: c.GetCNSIUserFromIAMToken, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeCertAuth, interfaces.AuthProvider{ - Handler: c.doCertAuthFlowRequest, - UserInfo: c.GetCNSIUserFromCertAuth, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeKubeConfigAz, interfaces.AuthProvider{ - Handler: c.doCertAuthFlowRequest, - UserInfo: c.GetCNSIUserFromCertAuth, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeGKE, interfaces.AuthProvider{ - Handler: c.doGKEFlowRequest, - UserInfo: c.GetGKEUserFromToken, - }) + // Register all of the providers + c.AddAuthProvider(auth.InitGKEKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitAWSKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitCertKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) + c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = "false" return nil diff --git a/src/jetstream/repository/interfaces/portal_proxy.go b/src/jetstream/repository/interfaces/portal_proxy.go index cfde40db4f..f8ce8d86b4 100644 --- a/src/jetstream/repository/interfaces/portal_proxy.go +++ b/src/jetstream/repository/interfaces/portal_proxy.go @@ -56,7 +56,7 @@ type PortalProxy interface { RefreshUAALogin(username, password string, store bool) error GetUserTokenInfo(tok string) (u *JWTUserTokenInfo, err error) GetUAAUser(userGUID string) (*ConnectedUser, error) - + // Proxy API requests ProxyRequest(c echo.Context, uri *url.URL) (map[string]*CNSIRequest, error) DoProxyRequest(requests []ProxyRequestInfo) (map[string]*CNSIRequest, error) @@ -65,10 +65,13 @@ type PortalProxy interface { // Database Connection GetDatabaseConnection() *sql.DB + AddAuthProvider(name string, provider AuthProvider) GetAuthProvider(name string) AuthProvider DoAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request, authHandler AuthHandlerFunc) (*http.Response, error) OAuthHandlerFunc(cnsiRequest *CNSIRequest, req *http.Request, refreshOAuthTokenFunc RefreshOAuthTokenFunc) AuthHandlerFunc + DoOAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) + GetCNSIUserFromOAuthToken(cnsiGUID string, cfTokenRecord *TokenRecord) (*ConnectedUser, bool) // Tokens - lower-level access SaveEndpointToken(cnsiGUID string, userGUID string, tokenRecord TokenRecord) error From c22658384641b386c62377a0eb313e7c544c9dc5 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 17 May 2019 09:01:47 +0100 Subject: [PATCH 30/34] Moer tidy ups --- src/jetstream/plugins/kubernetes/api_proxy.go | 66 +++++++++++++++ .../plugins/kubernetes/auth/awsiam.go | 8 +- .../plugins/kubernetes/auth/azure.go | 10 +-- src/jetstream/plugins/kubernetes/auth/cert.go | 8 +- .../cert_tests.go} | 6 +- src/jetstream/plugins/kubernetes/auth/gke.go | 7 +- src/jetstream/plugins/kubernetes/auth/oidc.go | 13 +-- .../plugins/kubernetes/auth/types.go | 5 +- .../plugins/kubernetes/auth_providers.go | 2 - .../plugins/kubernetes/endpoint_config.go | 4 - src/jetstream/plugins/kubernetes/go.mod | 5 +- src/jetstream/plugins/kubernetes/go.sum | 10 ++- .../plugins/kubernetes/helm_client.go | 80 +------------------ .../plugins/kubernetes/helm_versions.go | 10 ++- .../plugins/kubernetes/install_release.go | 15 ++-- .../plugins/kubernetes/kube_dashboard.go | 4 +- .../plugins/kubernetes/list_releases.go | 10 +-- src/jetstream/plugins/kubernetes/main.go | 30 +++---- .../monocular/20190307115300_ChartStore.go | 8 +- src/jetstream/plugins/monocular/endpoint.go | 4 +- src/jetstream/plugins/monocular/main.go | 8 +- src/jetstream/plugins/monocular/repository.go | 2 +- src/jetstream/plugins/monocular/sync.go | 11 +-- 23 files changed, 161 insertions(+), 165 deletions(-) create mode 100644 src/jetstream/plugins/kubernetes/api_proxy.go rename src/jetstream/plugins/kubernetes/{cert_requests_test.go => auth/cert_tests.go} (98%) diff --git a/src/jetstream/plugins/kubernetes/api_proxy.go b/src/jetstream/plugins/kubernetes/api_proxy.go new file mode 100644 index 0000000000..bcd14dd99b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/api_proxy.go @@ -0,0 +1,66 @@ +package kubernetes + +import ( + "fmt" + + // "k8s.io/client-go/rest" + + // Import the OIDC auth plugin + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +// KubeProxyError represents error when a proxied request to the Kube API failes +type KubeProxyError struct { + Name string +} + +// KubeProxyFunc represents a function to proxy to the Kube API +type KubeProxyFunc func(*interfaces.ConnectedEndpoint, chan KubeProxyResponse) + +// KubeProxyResponse represents a response from a proxy request to the Kube API +type KubeProxyResponse struct { + Endpoint string + Result interface{} + Error *KubeProxyError +} + +// KubeProxyResponses represents response from multiple proxy requests to the Kube API +type KubeProxyResponses map[string]interface{} + +// ProxyKubernetesAPI proxies an API request to all of the user's connected Kubernetes endpoints +func (c *KubernetesSpecification) ProxyKubernetesAPI(userID string, f KubeProxyFunc) (KubeProxyResponses, error) { + + var p = c.portalProxy + k8sList := make([]*interfaces.ConnectedEndpoint, 0) + eps, err := p.ListEndpointsByUser(userID) + if err != nil { + return nil, fmt.Errorf("Could not get endpints Client for endpoint: %v+", err) + } + + for _, endpoint := range eps { + if endpoint.CNSIType == "k8s" { + k8sList = append(k8sList, endpoint) + } + } + + // Check that we actually have some + // TODO + done := make(chan KubeProxyResponse) + for _, endpoint := range k8sList { + go f(endpoint, done) + } + + responses := make(KubeProxyResponses) + for range k8sList { + res := <-done + if res.Error == nil { + responses[res.Endpoint] = res.Result + } else { + responses[res.Endpoint] = res.Error + } + } + + return responses, nil +} diff --git a/src/jetstream/plugins/kubernetes/auth/awsiam.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go index 9c8e950605..5bdc813264 100644 --- a/src/jetstream/plugins/kubernetes/auth/awsiam.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -20,6 +20,7 @@ import ( "github.com/kubernetes-sigs/aws-iam-authenticator/pkg/token" ) +// AWSIAMUserInfo is the user info needed to connect to AWS Kubernetes type AWSIAMUserInfo struct { Cluster string `json:"cluster"` AccessKey string `json:"accessKey"` @@ -31,15 +32,16 @@ type AWSKubeAuth struct { portalProxy interfaces.PortalProxy } -const AuthConnectTypeAWSIAM = "aws-iam" +const authConnectTypeAWSIAM = "aws-iam" // InitAWSKubeAuth creates a GKEKubeAuth func InitAWSKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { return &AWSKubeAuth{portalProxy: portalProxy} } +// GetName returns the Auth Provider name func (c *AWSKubeAuth) GetName() string { - return AuthConnectTypeAWSIAM + return authConnectTypeAWSIAM } func (c *AWSKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -105,7 +107,7 @@ func (c *AWSKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Conte expiry := time.Now().Local().Add(time.Minute * time.Duration(15)) tokenRecord := c.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeAWSIAM + tokenRecord.AuthType = authConnectTypeAWSIAM return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go index 4f0f0ab41b..0447b2e528 100644 --- a/src/jetstream/plugins/kubernetes/auth/azure.go +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -6,14 +6,13 @@ import ( "io/ioutil" "time" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" ) -const AuthConnectTypeKubeConfigAz = "kubeconfig-az" - +const authConnectTypeKubeConfigAz = "kubeconfig-az" // AzureKubeAuth is Azure Authentication with Certificates type AzureKubeAuth struct { @@ -27,7 +26,7 @@ func InitAzureKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { // GetName returns the provider name func (c *AzureKubeAuth) GetName() string { - return AuthConnectTypeKubeConfigAz + return authConnectTypeKubeConfigAz } func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { @@ -64,12 +63,11 @@ func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Con expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeKubeConfigAz + tokenRecord.AuthType = authConnectTypeKubeConfigAz return &tokenRecord, &cnsiRecord, nil } - func (p *AzureKubeAuth) getAKSAuthConfig(k *config.KubeConfigUser) (*KubeCertificate, error) { if !isAKSAuth(k) { diff --git a/src/jetstream/plugins/kubernetes/auth/cert.go b/src/jetstream/plugins/kubernetes/auth/cert.go index 9e7569587b..517ef8a780 100644 --- a/src/jetstream/plugins/kubernetes/auth/cert.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -19,8 +19,7 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -const AuthConnectTypeCertAuth = "kube-cert-auth" - +const authConnectTypeCertAuth = "kube-cert-auth" // CertKubeAuth is GKE Authentication with Certificates type CertKubeAuth struct { @@ -32,8 +31,9 @@ func InitCertKubeAuth(portalProxy interfaces.PortalProxy) *CertKubeAuth { return &CertKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *CertKubeAuth) GetName() string { - return AuthConnectTypeCertAuth + return authConnectTypeCertAuth } func (c *CertKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -98,7 +98,7 @@ func (c *CertKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Cont expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) disconnected := false tokenRecord := c.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, disconnected) - tokenRecord.AuthType = AuthConnectTypeCertAuth + tokenRecord.AuthType = authConnectTypeCertAuth return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/cert_requests_test.go b/src/jetstream/plugins/kubernetes/auth/cert_tests.go similarity index 98% rename from src/jetstream/plugins/kubernetes/cert_requests_test.go rename to src/jetstream/plugins/kubernetes/auth/cert_tests.go index 223a08df53..9c1616fd10 100644 --- a/src/jetstream/plugins/kubernetes/cert_requests_test.go +++ b/src/jetstream/plugins/kubernetes/auth/cert_tests.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -11,7 +11,7 @@ import ( func TestFetchCertAuth(t *testing.T) { - kubeSpec := &KubernetesSpecification{} + kubeSpec := &CertKubeAuth{} mockConnectRequet := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBRENDQWVpZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRFNE1UQXhNakUyTlRnMU1Wb1hEVEU1TVRBeE16RTJOVGcxTVZvd01URVhNQlVHQTFVRQpDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGakFVQmdOVkJBTVREVzFwYm1scmRXSmxMWFZ6WlhJd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEeFNtT2dORmV5VjkyRE5Sa0RmNVNSZlk5WHhGRVkKbFF2Z3lEcmFnVVpCcXFTaFBaZGdFdnBFWlVObkVoa1ZaYnc5WGUxelFMdVFuZnJEaERzOHlqTk8xWTV4ZUt2cwpIQlVBZEsxa2FQMlBXMVhSN3hxREh2SFdhQ3dRWVdHK1Bsa0NXMzg1YzBsNktXRUc5SVlFWFdkT1VDbEZuT3ZoCnJBN2RTNWR5eFViU3FCd0RSSDljMlhDVjQ4c1dLZnJhdmQvMTg5bWJENStQRk9ZditnRFFycHBTYnUzQytjTmYKbC93NDl0L0NQWlQ4bHFWU2FBUHhzOXFKbStKSFVFWHk3a1p2bGRZbG83NjJ3V1B6c0JoaXNkNU5ua2hmbGhrUApKSWNrWjN5eWF5bkdkOWJlKzdva2hwVEt1STNReGh5MTBrMWsxc1dyYmJTWVJDbmQ1akdVN3N2REFnTUJBQUdqClB6QTlNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQ2NNS2IreVNOeG9wYmRGNwpqcTBTN1EvQmFUbWhzWnVUWDRPL2V4ZllkZFpwK0didDc2VEVqYWdmUmQvbXFFazlheFAxWDVzWkRhQ283blkzCitDdnpMb1h2Y3NHdnVnRTJSWStsSjdBbTdLS0lTdGVGTFdsRnBDTXJBWXF6TG1Wb3NyeDlWM0ZTbWY2REdWRk4KSmVVVFBnYTFrNTltMUNFSjZDYTAzM2hEYmp6aWsxd0xtR3pLVmRDR29HT1N0Vm1tbTZFWWMza1hheGVXTUtuMQpoRDhmREV5R3p1Z2hhTkQyYjZGdnlha28yUVQ3dFd3L09yMXNhQWQ1S2N5Wk4xdDVtUHI1RHh0SmFKNHN2dzh4Ck01R0MySFVUM2pzK3dQdDVSUlpSRXd2MkNCUVNHUWtxcDFrWTNLV1pXTDB2WEQvbjJqYlZGUUtacHQ2dDZKT3EKaVQ2bnlRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=:LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBOFVwam9EUlhzbGZkZ3pVWkEzK1VrWDJQVjhSUkdKVUw0TWc2Mm9GR1FhcWtvVDJYCllCTDZSR1ZEWnhJWkZXVzhQVjN0YzBDN2tKMzZ3NFE3UE1velR0V09jWGlyN0J3VkFIU3RaR2o5ajF0VjBlOGEKZ3g3eDFtZ3NFR0Zodmo1WkFsdC9PWE5KZWlsaEJ2U0dCRjFuVGxBcFJaenI0YXdPM1V1WGNzVkcwcWdjQTBSLwpYTmx3bGVQTEZpbjYycjNmOWZQWm13K2ZqeFRtTC9vQTBLNmFVbTd0d3ZuRFg1ZjhPUGJmd2oyVS9KYWxVbWdECjhiUGFpWnZpUjFCRjh1NUdiNVhXSmFPK3RzRmo4N0FZWXJIZVRaNUlYNVlaRHlTSEpHZDhzbXNweG5mVzN2dTYKSklhVXlyaU4wTVljdGRKTlpOYkZxMjIwbUVRcDNlWXhsTzdMd3dJREFRQUJBb0lCQVFESEVJd291NFl1U0hjagpyRWE2c0NLdDlWeXhGL0dmeWpkR2QycTJvamlJTEhRdDRsWmttTU9JY2RLdDBpeUhqcXRDSlora21oOGtMSEdaCnBCb0xDUFpUYjdSWXdTbDFYYVdsL3B5ZVhrL3lXWFB3QXNkb3JicnZISHBkK1RsZWJxbVlYRXdWNVpzVkFkWmUKbXBXR1BGamlMeGdkcWx5Z2pnYWxZNXZLd0I2eDR3MHZTczdaUVBoQkFqTSs3a2Q3ZUpBRUJHZUxRL1lRTEIvVgo1Qmtrci9mT2d6K1A3V2t5QUovRk1iRzY4K0E3Z1FHeTJvWGdnbHVsOXg5YjlkcXJDSm5NVHFkNENvbU5qTmFjCkpValhjMEVGeFJqTjdOR0djenhXREVnc1dicGMxeFpYK20zcEQrQnRDd3BSeXJ4Q3NDWGwrTllnT01Jb1FZaWIKaUxQZzhsM0JBb0dCQVBQa3BMdTM0U1NhQVJNOE1YMWxCckdMa0k2cjlLZW5MaVZNYU55bG9CMWtyM29DaEJueApiek5kMkdxaXNBbEJFb3JVWEdkOVBadVNqYmRGMnVxTGgvdVMvdlBBMUpmR3drakZERmNyTVlXeDExWGVMcy9RCmYrdjF2dmQ5RUp5b0w2ekIxQkhaelZuenFIQ2w4OWlvNEY3Wkl3MmlKQ0NjWlJiN2JlOUdzbkdiQW9HQkFQMUUKckR3bldQNG5XN00wdlJYcFVkY3Q1UE96dHZIVFpoNTFJaC93NklOQzladXl3eFJoMi9XTEs0ZlZBRDYxeFloTgpVR0FvU2FUQzRrZWdoYlUvTWY5aWtBS3c3MWRPdk5HSzBSdjROTlZwbEJWbFhhcnF5OEpTeGJIcnNNU25rSTI4CjhNZ21YdlYvNkYwZTUxWFl1bHlYUXNqVWpCbVRXUTUzZnRWcXRhVDVBb0dCQU9vRkpod0pJRHNpbW8xK1lHNVYKbGNxZWhDS2gxS3RadXVtSEc4YzhGUnFmRmRFWXdQQ3p2V09vVkpSZGJsUXk0RHZkOEp4TWkrVFBCclFvanhvbQpzR0F3ZC9vanVObTVtWXFCcUltcnBHVUljL3FzcW5ZMU5jbVBqNkdobTJMMTdtanh3eTh0c2VEeDcxbkhvdWJ0CmcveitsS2ZzUUlZYUN0VzJnNUhvWUNpcEFvR0JBTTNtcHAvQTNYakNScXJLbFc3YTRNNHZZWk0rNTl4eUlQTmkKQnZ3d3Z0YjMrUFU3djUweWNjQ09CRFhKMVFrbWZoRHh5Z1ppdW54WWM5NEhncXgzVkE1cjh1ZzlNRmVxaTVkUApZL0Y1T0hySCtydnFUTnhIUnFBVTZ1UmEyTHNILzEwNzNnVGFMUmtwZzU4eElLR0tNUGhWZ05ZRTltRlVpWEpaCmM2UE52UjhCQW9HQU0zNTlZSUNUalNNYlYvSjJUbUx1Zjh2ejh0WVJDeXlla0h6Z2tTcmpqWUZWNmoxUTllWGQKa1ZFWitaYW1xYkZvUmxqK1lHS21XQ0w0OXJTWkYxNmVYL1V3OW5haEErcmtYL21aaERkOXIrVE90enpycUVYSQpUQVlld3R5ZGEzUzkyaytOWU8zZE5kbWZLQVdYV2JzdjZUKzBBTXR6VTFkMlNaaDlsTU5aVkUwPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" e := echo.New() @@ -41,7 +41,7 @@ func TestFetchCertAuth(t *testing.T) { t.Fail() } - testKubeCertAuth := &KubeCertAuth{} + testKubeCertAuth := &KubeCertificate{} err = json.NewDecoder(strings.NewReader(jsonString)).Decode(testKubeCertAuth) if err != nil { t.Fail() diff --git a/src/jetstream/plugins/kubernetes/auth/gke.go b/src/jetstream/plugins/kubernetes/auth/gke.go index 3bdeced3b4..9a1193d4ad 100644 --- a/src/jetstream/plugins/kubernetes/auth/gke.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -37,15 +37,16 @@ type GKEKubeAuth struct { portalProxy interfaces.PortalProxy } -const AuthConnectTypeGKE = "gke-auth" +const authConnectTypeGKE = "gke-auth" // InitGKEKubeAuth creates a GKEKubeAuth func InitGKEKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { return &GKEKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *GKEKubeAuth) GetName() string { - return AuthConnectTypeGKE + return authConnectTypeGKE } func (c *GKEKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -107,7 +108,7 @@ func (c *GKEKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Conte // Create a new token record - we need to store the client ID and secret as well, so cheekily use the refresh token for this tokenRecord := c.portalProxy.InitEndpointTokenRecord(0, oauthToken.AccessToken, string(tokenInfo), false) - tokenRecord.AuthType = AuthConnectTypeGKE + tokenRecord.AuthType = authConnectTypeGKE return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 80ce00dda0..199deee5ca 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -4,17 +4,17 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "io/ioutil" + "net/http" "time" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - log "github.com/sirupsen/logrus" + "github.com/SermoDigital/jose/jws" "github.com/labstack/echo" + log "github.com/sirupsen/logrus" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "github.com/SermoDigital/jose/jws" ) type KubeConfigAuthProviderOIDC struct { @@ -26,7 +26,7 @@ type KubeConfigAuthProviderOIDC struct { Expiry time.Time } -const AuthConnectTypeOIDC = "OIDC" +const authConnectTypeOIDC = "OIDC" // OIDCKubeAuth type OIDCKubeAuth struct { @@ -38,8 +38,9 @@ func InitOIDCKubeAuth(portalProxy interfaces.PortalProxy) *OIDCKubeAuth { return &OIDCKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *OIDCKubeAuth) GetName() string { - return AuthConnectTypeOIDC + return authConnectTypeOIDC } func (c *OIDCKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go index 127ac705b1..9a32914bc9 100644 --- a/src/jetstream/plugins/kubernetes/auth/types.go +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -21,9 +21,6 @@ type KubeAuthProvider interface { GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) } - -// ------------------------------- - // KubeCertificate represents certificate infor for Kube Authentication type KubeCertificate struct { Certificate string `json:"cert"` @@ -47,4 +44,4 @@ func (k *KubeCertificate) GetCerticate() (tls.Certificate, error) { return tls.Certificate{}, err } return cert, nil -} \ No newline at end of file +} diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go index c078f0f61a..dcd73d2ca3 100644 --- a/src/jetstream/plugins/kubernetes/auth_providers.go +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -7,8 +7,6 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -// Interface for Kubernetes Authentication Providers - var kubeAuthProviders map[string]auth.KubeAuthProvider // AddAuthProvider adds a Kubernetes auth provider diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index f047d86898..22f3521f10 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -16,8 +16,6 @@ import ( func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { - log.Info("GetConfigForEndpoint") - name := "cluster-0" // Create a config @@ -50,8 +48,6 @@ func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token i func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { log.Debug("addAuthInfoForEndpoint") - log.Warn(tokenRec.AuthType) - var authProvider = c.GetAuthProvider(tokenRec.AuthType) if authProvider == nil { return errors.New("Unsupported auth type") diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index b9056ff45a..5393fae8ef 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -18,7 +18,6 @@ require ( github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect github.com/go-openapi/spec v0.18.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.2.1 // indirect @@ -31,6 +30,7 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/hashicorp/golang-lru v0.5.0 // indirect github.com/helm/monocular v1.4.0 + github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000 github.com/heptio/authenticator v0.3.0 // indirect github.com/huandu/xstrings v1.2.0 // indirect github.com/imdario/mergo v0.3.7 // indirect @@ -45,14 +45,12 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.1 // indirect - github.com/prometheus/client_golang v0.9.2 // indirect github.com/russross/blackfriday v2.0.0+incompatible // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v0.0.3 // indirect github.com/spf13/pflag v1.0.3 // indirect github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 // indirect - github.com/unrolled/render v1.0.0 // indirect golang.org/x/net v0.0.0-20190225153610-fe579d43d832 // indirect golang.org/x/oauth2 v0.0.0-20190226191147-529b322ea346 // indirect golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect @@ -78,6 +76,7 @@ require ( replace ( github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces => ../../repository/interfaces + github.com/helm/monocular/chartsvc => ../monocular/chartsvc github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2 github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0 diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index 836c47b4bc..69a959d32c 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -33,6 +33,7 @@ github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrP github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= @@ -75,6 +76,8 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= @@ -96,6 +99,8 @@ github.com/helm/monocular v1.6.0 h1:zE8OggduPEtnF2x1XgrQnMwnKFUGCvk/RNs0NX8hgjg= github.com/helm/monocular v1.7.0 h1:SW2xr6KoVJtZT6ZAtON8jJVLqcRE3C8pBb9kyo8Icxw= github.com/heptio/authenticator v0.3.0 h1:Xh6XWkLZ+CksGuky+vsr77mHnI9C4L3nwj+xuZu28J0= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= +github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= +github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= @@ -154,9 +159,9 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= +github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= @@ -172,12 +177,15 @@ github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= github.com/unrolled/render v1.0.0 h1:XYtvhA3UkpB7PqkvhUFYmpKD55OudoIeygcfus4vcd4= github.com/unrolled/render v1.0.0/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 2bbb9e8148..7c04053da1 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -7,31 +7,15 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/portforwarder" "k8s.io/helm/pkg/kube" // Import the OIDC auth plugin _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -type KubeProxyError struct { - Name string -} - -type KubeProxyFunc func(*interfaces.ConnectedEndpoint, chan KubeProxyResponse) - -type KubeProxyResponse struct { - Endpoint string - Result interface{} - Error *KubeProxyError -} - -type KubeProxyResponses map[string]interface{} - +// GetHelmClient gets a client that can be used to talk to Tiller func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (helm.Interface, *kubernetes.Clientset, *kube.Tunnel, error) { // Need to get a config object for the target endpoint var p = c.portalProxy @@ -64,73 +48,13 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he return nil, nil, nil, err } - log.Warnf("Tiller tunnel is using Port: %d", tillerTunnel.Local) - - // TODO: Concurrency? + log.Debugf("Tiller tunnel is using Port: %d", tillerTunnel.Local) tillerHost := fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local) - client := newClient(tillerHost) return client, kubeClient, tillerTunnel, nil } -func (c *KubernetesSpecification) ProxyKubernetesAPI(userID string, f KubeProxyFunc) (KubeProxyResponses, error) { - - var p = c.portalProxy - k8sList := make([]*interfaces.ConnectedEndpoint, 0) - eps, err := p.ListEndpointsByUser(userID) - if err != nil { - return nil, fmt.Errorf("Could not get endpints Client for endpoint: %v+", err) - } - - for _, endpoint := range eps { - if endpoint.CNSIType == "k8s" { - k8sList = append(k8sList, endpoint) - } - } - - // Check that we actually have some - // TODO - done := make(chan KubeProxyResponse) - for _, endpoint := range k8sList { - go f(endpoint, done) - } - - responses := make(KubeProxyResponses) - for range k8sList { - res := <-done - if res.Error == nil { - responses[res.Endpoint] = res.Result - } else { - responses[res.Endpoint] = res.Error - } - } - - return responses, nil -} - -// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. -func configForContext(context string, kubeconfig string) (*rest.Config, error) { - config, err := kube.GetConfig(context, kubeconfig).ClientConfig() - if err != nil { - return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) - } - return config, nil -} - -// getKubeClient creates a Kubernetes config and client for a given kubeconfig context. -func getKubeClient(context string, kubeconfig string) (*rest.Config, kubernetes.Interface, error) { - config, err := configForContext(context, kubeconfig) - if err != nil { - return nil, nil, err - } - client, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) - } - return config, client, nil -} - func newClient(tillerHost string) helm.Interface { options := []helm.Option{helm.Host(tillerHost), helm.ConnectTimeout(20)} return helm.NewClient(options...) diff --git a/src/jetstream/plugins/kubernetes/helm_versions.go b/src/jetstream/plugins/kubernetes/helm_versions.go index c3bc1ae91f..1a21f5dbae 100644 --- a/src/jetstream/plugins/kubernetes/helm_versions.go +++ b/src/jetstream/plugins/kubernetes/helm_versions.go @@ -1,6 +1,8 @@ package kubernetes import ( + "net/http" + "github.com/labstack/echo" log "github.com/sirupsen/logrus" @@ -23,7 +25,13 @@ func (c *KubernetesSpecification) GetHelmVersions(ec echo.Context) error { userID := ec.Get("user_id").(string) resp, err := c.ProxyKubernetesAPI(userID, c.fetchHelmVersion) - log.Warn(err) + if err != nil { + return interfaces.NewHTTPShadowError( + http.StatusInternalServerError, + "Error fetching Helm Tiller Versions", + "Error fetching Helm Tiller Versions: %v", err, + ) + } return ec.JSON(200, resp) } diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 1625e5600e..b6fc1ff755 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -51,24 +51,19 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { log.Info("Installing release") log.Info(chartID) - downloadURL, err := c.GetChart(chartID, params.Chart.Version) + downloadURL, err := c.getChart(chartID, params.Chart.Version) if err != nil { return fmt.Errorf("Could not get the Download URL") } log.Debugf("Chart Download URL: %s", downloadURL) - // Get IO reader for the Chart - // Should we ignore SSL certs? // TODO: Look up Helm Repository endpoiint and use the value from that http := c.portalProxy.GetHttpClient(true) resp, err := http.Get(downloadURL) - // // Check StatusCode is 200 - // // Body is a io.ReadCloser - if resp.StatusCode != 200 { return fmt.Errorf("Could not download Chart Archive: %s", resp.Status) } @@ -79,7 +74,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return fmt.Errorf("Could not load chart from archive: %v+", err) } - log.Debug("Loaded chart") + log.Debug("Loaded helm chart") endpointGUID := params.Endpoint userGUID := ec.Get("user_id").(string) @@ -94,7 +89,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { if _, err := chartutil.LoadRequirements(chart); err == nil { log.Debug("Chart requirements loaded") } else if err != chartutil.ErrRequirementsNotFound { - log.Error("Can not load requirements") + log.Error("Can not load requirements for helm chart") } else { log.Error(err) } @@ -120,7 +115,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return ec.JSON(200, installResponse) } -func (c *KubernetesSpecification) GetChart(chartID, version string) (string, error) { +func (c *KubernetesSpecification) getChart(chartID, version string) (string, error) { helm := c.portalProxy.GetPlugin("monocular") if helm == nil { @@ -167,7 +162,7 @@ func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error { deleteResponse, err := client.DeleteRelease(releaseName, helm.DeletePurge(true)) if err != nil { return fmt.Errorf("Could not delete Helm Release: %v+", err) - } + return ec.JSON(200, deleteResponse) } diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index f2d37a4b65..76e9dc4c69 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -149,7 +149,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { log.Info("Making request") req := c.Request() w := c.Response().Writer - log.Info("%v+", req) + log.Infof("%v+", req) // if h.tryUpgrade(w, req) { // return @@ -220,7 +220,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { log.Debugf("%v+", response.Header) response.Header.Del("X-FRAME-OPTIONS") response.Header.Set("X-FRAME-OPTIONS", "sameorigin") - log.Debug("%v+", response) + log.Debugf("%v+", response) return nil } diff --git a/src/jetstream/plugins/kubernetes/list_releases.go b/src/jetstream/plugins/kubernetes/list_releases.go index ef0f45e250..c920c888a5 100644 --- a/src/jetstream/plugins/kubernetes/list_releases.go +++ b/src/jetstream/plugins/kubernetes/list_releases.go @@ -52,30 +52,30 @@ func (c *KubernetesSpecification) listReleases(ep *interfaces.ConnectedEndpoint, Result: nil, } - log.Warnf("listReleases: START: %s", ep.GUID) + log.Debugf("listReleases: START: %s", ep.GUID) client, _, tiller, err := c.GetHelmClient(ep.GUID, ep.Account) if err != nil { - log.Warnf("listReleases: CLIENT_ERROR: %s", ep.GUID) + log.Debugf("listReleases: CLIENT_ERROR: %s", ep.GUID) done <- response return } defer tiller.Close() - log.Warnf("listReleases: REQUEST: %s", ep.GUID) + log.Debugf("listReleases: REQUEST: %s", ep.GUID) res, err := client.ListReleases( helm.ReleaseListStatuses(nil), ) if err != nil { - log.Warnf("listReleases: ERROR: %s", ep.GUID) + log.Debugf("listReleases: ERROR: %s", ep.GUID) log.Error(err) done <- response return } - log.Warnf("listReleases: OK: %s", ep.GUID) + log.Debugf("listReleases: OK: %s", ep.GUID) response.Result = res done <- response } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 26a36b433d..736ddfa35d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -12,26 +12,22 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" ) +// KubernetesSpecification is the endpoint that adds Kubernetes support to the backend type KubernetesSpecification struct { portalProxy interfaces.PortalProxy endpointType string } -// KubeDashboardPluginConfigSetting is config value send back to the client to indicate if the kube dashboard can be navigated to -const KubeDashboardPluginConfigSetting = "kubeDashboardEnabled" - const ( - EndpointType = "k8s" - CLIENT_ID_KEY = "K8S_CLIENT" - AuthConnectTypeKubeConfig = "KubeConfig" - AuthConnectTypeKubeConfigAz = "kubeconfig-az" - AuthConnectTypeAWSIAM = "aws-iam" - AuthConnectTypeCertAuth = "kube-cert-auth" - AuthConnectTypeGKE = "gke-auth" + kubeEndpointType = "k8s" + defaultKubeClientID = "K8S_CLIENT" + + // kubeDashboardPluginConfigSetting is config value send back to the client to indicate if the kube dashboard can be navigated to + kubeDashboardPluginConfigSetting = "kubeDashboardEnabled" ) func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { - return &KubernetesSpecification{portalProxy: portalProxy, endpointType: EndpointType}, nil + return &KubernetesSpecification{portalProxy: portalProxy, endpointType: kubeEndpointType}, nil } func (c *KubernetesSpecification) GetEndpointPlugin() (interfaces.EndpointPlugin, error) { @@ -47,11 +43,11 @@ func (c *KubernetesSpecification) GetMiddlewarePlugin() (interfaces.MiddlewarePl } func (c *KubernetesSpecification) GetType() string { - return EndpointType + return kubeEndpointType } func (c *KubernetesSpecification) GetClientId() string { - return c.portalProxy.Env().String(CLIENT_ID_KEY, "k8s") + return c.portalProxy.Env().String(defaultKubeClientID, "k8s") } func (c *KubernetesSpecification) Register(echoContext echo.Context) error { @@ -72,7 +68,7 @@ func (c *KubernetesSpecification) Validate(userGUID string, cnsiRecord interface return nil } -func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userId string) (*interfaces.TokenRecord, bool, error) { +func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userID string) (*interfaces.TokenRecord, bool, error) { log.Debug("Kubernetes Connect...") connectType := ec.FormValue("connect_type") @@ -100,8 +96,8 @@ func (c *KubernetesSpecification) Init() error { c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) - - c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = "false" + + c.portalProxy.GetConfig().PluginConfig[kubeDashboardPluginConfigSetting] = "false" return nil } @@ -129,7 +125,7 @@ func (c *KubernetesSpecification) Info(apiEndpoint string, skipSSLValidation boo var v2InfoResponse interfaces.V2Info var newCNSI interfaces.CNSIRecord - newCNSI.CNSIType = EndpointType + newCNSI.CNSIType = kubeEndpointType _, err := url.Parse(apiEndpoint) if err != nil { diff --git a/src/jetstream/plugins/monocular/20190307115300_ChartStore.go b/src/jetstream/plugins/monocular/20190307115300_ChartStore.go index 9eed7d37d7..409fb3691b 100644 --- a/src/jetstream/plugins/monocular/20190307115300_ChartStore.go +++ b/src/jetstream/plugins/monocular/20190307115300_ChartStore.go @@ -2,6 +2,7 @@ package monocular import ( "database/sql" + "strings" "bitbucket.org/liamstask/goose/lib/goose" @@ -31,6 +32,11 @@ func init() { return err } + binaryDataType := "BYTEA" + if strings.Contains(conf.Driver.Name, "mysql") { + binaryDataType = "BLOB" + } + createChartFilesTable := "CREATE TABLE IF NOT EXISTS chart_files (" createChartFilesTable += "id VARCHAR(255) NOT NULL," createChartFilesTable += "filename VARCHAR(64) NOT NULL," @@ -38,7 +44,7 @@ func init() { createChartFilesTable += "name VARCHAR(255) NOT NULL," createChartFilesTable += "repo_name VARCHAR(255) NOT NULL," createChartFilesTable += "digest VARCHAR(255) NOT NULL," - createChartFilesTable += "content BLOB," + createChartFilesTable += "content " + binaryDataType + "," createChartFilesTable += "PRIMARY KEY (id, filename) );" _, err = txn.Exec(createChartFilesTable) diff --git a/src/jetstream/plugins/monocular/endpoint.go b/src/jetstream/plugins/monocular/endpoint.go index 66ef809295..8bf5ca906a 100644 --- a/src/jetstream/plugins/monocular/endpoint.go +++ b/src/jetstream/plugins/monocular/endpoint.go @@ -10,7 +10,7 @@ import ( ) func (m *Monocular) GetType() string { - return EndpointType + return helmEndpointType } func (m *Monocular) GetClientId() string { @@ -37,7 +37,7 @@ func (m *Monocular) Info(apiEndpoint string, skipSSLValidation bool) (interfaces var v2InfoResponse interfaces.V2Info var newCNSI interfaces.CNSIRecord - newCNSI.CNSIType = EndpointType + newCNSI.CNSIType = helmEndpointType _, err := url.Parse(apiEndpoint) if err != nil { diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 260c80ff9d..2336033fa0 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -15,7 +15,7 @@ import ( ) const ( - EndpointType = "helm" + helmEndpointType = "helm" ) const prefix = "/pp/v1/chartsvc/" @@ -70,7 +70,7 @@ func (m *Monocular) syncOnStartup() { helmRepos := make([]string, 0) for _, ep := range endpoints { - if ep.CNSIType == "helm" { + if ep.CNSIType == helmEndpointType { helmRepos = append(helmRepos, ep.Name) // Is this an endpoint that we don't have charts for ? @@ -88,7 +88,7 @@ func (m *Monocular) syncOnStartup() { endpoint := &interfaces.CNSIRecord{ GUID: repo, Name: repo, - CNSIType: "helm", + CNSIType: helmEndpointType, } m.Sync(interfaces.EndpointUnregisterAction, endpoint) } @@ -155,7 +155,7 @@ func (m *Monocular) ConfigureSQL() error { } func (m *Monocular) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { - if endpoint.CNSIType == EndpointType { + if endpoint.CNSIType == helmEndpointType { m.Sync(action, endpoint) } } diff --git a/src/jetstream/plugins/monocular/repository.go b/src/jetstream/plugins/monocular/repository.go index c1d3424fba..3bb93d5f04 100644 --- a/src/jetstream/plugins/monocular/repository.go +++ b/src/jetstream/plugins/monocular/repository.go @@ -26,7 +26,7 @@ func (m *Monocular) ListRepos(c echo.Context) error { repos := make([]HelmRepoInfo, 0) for _, ep := range endpoints { - if ep.CNSIType == EndpointType { + if ep.CNSIType == helmEndpointType { // Helm endpoint repo := HelmRepoInfo{ ID: ep.Name, diff --git a/src/jetstream/plugins/monocular/sync.go b/src/jetstream/plugins/monocular/sync.go index 29f93525f5..9513c243e4 100644 --- a/src/jetstream/plugins/monocular/sync.go +++ b/src/jetstream/plugins/monocular/sync.go @@ -2,6 +2,7 @@ package monocular import ( "encoding/json" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/helm/monocular/chartrepo" log "github.com/sirupsen/logrus" @@ -20,10 +21,12 @@ type SyncMetadata struct { // Sync Chanel var syncChan = make(chan SyncJob, 100) +// InitSync starts the go routine that will sync repositories in the background func (m *Monocular) InitSync() { go m.processSyncRequests() } +// Sync shceudles a sync action for the given endpoint func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { job := SyncJob{ @@ -31,19 +34,17 @@ func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces. Endpoint: endpoint, } - log.Warn("Scheduling Sync job") syncChan <- job } func (m *Monocular) processSyncRequests() { log.Info("Helm Repository Sync init") for job := range syncChan { - log.Info("Processing Job") - log.Info(job.Endpoint.Name) + log.Debugf("Processing Helm Repository Sync Job: %s", job.Endpoint.Name) // Could be delete or sync if job.Action == 0 { - log.Info("Syncing new repository") + log.Debug("Syncing new repository") metadata := SyncMetadata{ Status: "Synchronizing", Busy: true, @@ -66,7 +67,7 @@ func (m *Monocular) processSyncRequests() { } } - log.Info("processSyncRequests finished") + log.Debug("processSyncRequests finished") } func marshalSyncMetadata(metadata SyncMetadata) string { From 9c9722d7efca8592cfaa5df1a758488b78c92131 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 17 May 2019 15:29:37 +0100 Subject: [PATCH 31/34] Fix helm sql datastore to return values.yaml --- .../plugins/monocular/chartsvc/datastore.go | 1 + .../plugins/monocular/chartsvc/handler.go | 20 +++++++++++-------- .../plugins/monocular/chartsvc/mongodb.go | 6 ++++++ .../plugins/monocular/sql_datastore.go | 11 ++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/jetstream/plugins/monocular/chartsvc/datastore.go b/src/jetstream/plugins/monocular/chartsvc/datastore.go index 98e0306e71..63cb377fe5 100644 --- a/src/jetstream/plugins/monocular/chartsvc/datastore.go +++ b/src/jetstream/plugins/monocular/chartsvc/datastore.go @@ -12,4 +12,5 @@ type ChartSvcDatastore interface { GetChartVersion(chartID, version string) (models.Chart, error) GetChartIcon(chartID string) ([]byte, error) GetChartVersionReadme(chartID, version string) ([]byte, error) + GetChartVersionValuesYaml(chartID, version string) ([]byte, error) } diff --git a/src/jetstream/plugins/monocular/chartsvc/handler.go b/src/jetstream/plugins/monocular/chartsvc/handler.go index 96ce07bf2e..402bd12588 100644 --- a/src/jetstream/plugins/monocular/chartsvc/handler.go +++ b/src/jetstream/plugins/monocular/chartsvc/handler.go @@ -173,17 +173,21 @@ func getChartVersionReadme(w http.ResponseWriter, req *http.Request, params Para // getChartVersionValues returns the values.yaml for a given chart func getChartVersionValues(w http.ResponseWriter, req *http.Request, params Params) { - db, closer := dbSession.DB() - defer closer() - var files models.ChartFiles - fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"]) - if err := db.C(filesCollection).FindId(fileID).One(&files); err != nil { - log.WithError(err).Errorf("could not find values.yaml with id %s", fileID) + chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) + version := params["version"] + fileID := fmt.Sprintf("%s-%s", chartID, version) + values, err := dataStore.GetChartVersionValuesYaml(chartID, version) + if err != nil { + log.WithError(err).Errorf("could not find files with id %s", fileID) http.NotFound(w, req) return } - - w.Write([]byte(files.Values)) + if len(values) == 0 { + log.Errorf("could not find a values.yaml for id %s", fileID) + http.NotFound(w, req) + return + } + w.Write(values) } // listChartsWithFilters returns the list of repos that contains the given chart and the latest version found diff --git a/src/jetstream/plugins/monocular/chartsvc/mongodb.go b/src/jetstream/plugins/monocular/chartsvc/mongodb.go index bcb0aac1d9..b9b2f70388 100644 --- a/src/jetstream/plugins/monocular/chartsvc/mongodb.go +++ b/src/jetstream/plugins/monocular/chartsvc/mongodb.go @@ -2,7 +2,9 @@ package chartsvc import ( //"bytes" + "errors" "fmt" + "github.com/globalsign/mgo/bson" "github.com/helm/monocular/chartsvc/models" "github.com/kubeapps/common/datastore" @@ -86,3 +88,7 @@ func (m *MongoDBChartSvcDatastore) GetChartVersionReadme(chartID, version string readme := []byte(files.Readme) return readme, nil } + +func (m *MongoDBChartSvcDatastore) GetChartVersionValuesYaml(chartID, version string) ([]byte, error) { + return nil, errors.New("Not implemented") +} diff --git a/src/jetstream/plugins/monocular/sql_datastore.go b/src/jetstream/plugins/monocular/sql_datastore.go index ccb09dc339..9fdf161069 100644 --- a/src/jetstream/plugins/monocular/sql_datastore.go +++ b/src/jetstream/plugins/monocular/sql_datastore.go @@ -322,6 +322,17 @@ func (s *SQLDBCMonocularDatastore) GetChartVersionReadme(chartID, version string return content, nil } +func (s *SQLDBCMonocularDatastore) GetChartVersionValuesYaml(chartID, version string) ([]byte, error) { + var content []byte + fileID := fmt.Sprintf("%s-%s", chartID, version) + err := s.db.QueryRow(getChartFileByID, fileID, "values").Scan(&content) + if err != nil { + return nil, fmt.Errorf("Unable to scan chart file record: %v", err) + } + + return content, nil +} + // ListRepositories gets all repository names func (s *SQLDBCMonocularDatastore) ListRepositories() ([]string, error) { rows, err := s.db.Query(getRepositories) From ad64cc3365581e9b79f8ecf39ecd2de32dc7853a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 20 May 2019 15:46:09 +0100 Subject: [PATCH 32/34] Fix OIDC Token refresh --- src/jetstream/auth_providers.go | 6 ++++++ src/jetstream/main.go | 2 +- src/jetstream/oidc_requests.go | 6 +++--- src/jetstream/passthrough.go | 2 +- src/jetstream/plugins/kubernetes/auth/awsiam.go | 8 ++++++++ src/jetstream/plugins/kubernetes/auth/azure.go | 10 ++++++++++ src/jetstream/plugins/kubernetes/auth/cert.go | 8 ++++++++ src/jetstream/plugins/kubernetes/auth/gke.go | 8 ++++++++ .../plugins/kubernetes/auth/kubeconfig.go | 10 +++++++++- src/jetstream/plugins/kubernetes/auth/oidc.go | 16 +++++++++++++++- src/jetstream/plugins/kubernetes/auth/types.go | 5 +++++ .../plugins/kubernetes/auth_providers.go | 9 ++------- .../repository/interfaces/portal_proxy.go | 7 ++++--- src/jetstream/repository/interfaces/structs.go | 4 +--- 14 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/jetstream/auth_providers.go b/src/jetstream/auth_providers.go index 77a33e30b1..83de6a3ce8 100644 --- a/src/jetstream/auth_providers.go +++ b/src/jetstream/auth_providers.go @@ -13,3 +13,9 @@ func (p *portalProxy) AddAuthProvider(name string, provider interfaces.AuthProvi func (p *portalProxy) GetAuthProvider(name string) interfaces.AuthProvider { return p.AuthProviders[name] } + +func (p *portalProxy) HasAuthProvider(name string) bool { + _, ok := p.AuthProviders[name] + return ok +} + diff --git a/src/jetstream/main.go b/src/jetstream/main.go index 47bd7dccb6..f31b701aef 100644 --- a/src/jetstream/main.go +++ b/src/jetstream/main.go @@ -586,7 +586,7 @@ func newPortalProxy(pc interfaces.PortalConfig, dcp *sql.DB, ss HttpSessionStore // OIDC pp.AddAuthProvider(interfaces.AuthTypeOIDC, interfaces.AuthProvider{ - Handler: pp.doOidcFlowRequest, + Handler: pp.DoOidcFlowRequest, }) return pp diff --git a/src/jetstream/oidc_requests.go b/src/jetstream/oidc_requests.go index cff3a76a80..67615548a7 100644 --- a/src/jetstream/oidc_requests.go +++ b/src/jetstream/oidc_requests.go @@ -9,15 +9,15 @@ import ( log "github.com/sirupsen/logrus" ) -func (p *portalProxy) doOidcFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { - log.Debug("doOidcFlowRequest") +func (p *portalProxy) DoOidcFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoOidcFlowRequest") authHandler := p.OAuthHandlerFunc(cnsiRequest, req, p.RefreshOidcToken) return p.DoAuthFlowRequest(cnsiRequest, req, authHandler) } func (p *portalProxy) RefreshOidcToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { - log.Debug("refreshToken") + log.Debug("RefreshOidcToken") userToken, ok := p.GetCNSITokenRecordWithDisconnected(cnsiGUID, userGUID) if !ok { return t, fmt.Errorf("Info could not be found for user with GUID %s", userGUID) diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index 604e034989..ea6499475f 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -398,7 +398,7 @@ func (p *portalProxy) doRequest(cnsiRequest *interfaces.CNSIRequest, done chan<- // Copy original headers through, except custom portal-proxy Headers fwdCNSIStandardHeaders(cnsiRequest, req) - // Find the auth provider for the auth type - default ot oauthflow + // Find the auth provider for the auth type - default to oauthflow authHandler := p.GetAuthProvider(tokenRec.AuthType) if authHandler.Handler != nil { res, err = authHandler.Handler(cnsiRequest, req) diff --git a/src/jetstream/plugins/kubernetes/auth/awsiam.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go index 5bdc813264..8ff7f6f862 100644 --- a/src/jetstream/plugins/kubernetes/auth/awsiam.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -143,6 +143,14 @@ func (c *AWSKubeAuth) getTokenIAM(info AWSIAMUserInfo) (string, error) { return tok.Token, nil } +func (c *AWSKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} + func (c *AWSKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doAWSIAMFlowRequest") diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go index 0447b2e528..88f239d1f4 100644 --- a/src/jetstream/plugins/kubernetes/auth/azure.go +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -98,3 +98,13 @@ func isAKSAuth(k *config.KubeConfigUser) bool { } return true } + + +func (c *AzureKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} + diff --git a/src/jetstream/plugins/kubernetes/auth/cert.go b/src/jetstream/plugins/kubernetes/auth/cert.go index 517ef8a780..9a067a755d 100644 --- a/src/jetstream/plugins/kubernetes/auth/cert.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -175,3 +175,11 @@ func (c *CertKubeAuth) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUI return userToken, nil } + +func (c *CertKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/gke.go b/src/jetstream/plugins/kubernetes/auth/gke.go index 9a1193d4ad..a05a794365 100644 --- a/src/jetstream/plugins/kubernetes/auth/gke.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -193,3 +193,11 @@ func (c *GKEKubeAuth) refreshGKEToken(skipSSLValidation bool, clientID, clientSe err = json.Unmarshal(respBody, &tokenInfo) return tokenInfo, err } + +func (c *GKEKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go index 085a9d82ac..0af53ac0b5 100644 --- a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -4,7 +4,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -const AuthConnectTypeKubeConfig = "KubeConfig" +const AuthConnectTypeKubeConfig = "KubeConfig" // KubeConfigAuth is same as OIDC with different name type KubeConfigAuth struct { @@ -19,3 +19,11 @@ func InitKubeConfigAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { func (c *KubeConfigAuth) GetName() string { return AuthConnectTypeKubeConfig } + +func (c *KubeConfigAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.portalProxy.DoOidcFlowRequest, + UserInfo: nil, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 199deee5ca..c61dd0ae16 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -151,5 +151,19 @@ func (c *OIDCKubeAuth) GetOIDCConfig(k *config.KubeConfigUser) (*KubeConfigAuthP func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("DoFlowRequest (OIDC)") - return c.portalProxy.DoOAuthFlowRequest(cnsiRequest, req) + return c.portalProxy.DoOidcFlowRequest(cnsiRequest, req) +} + +func (c *OIDCKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // No need to register OIDC, as its already built in + existing := c.portalProxy.HasAuthProvider(c.GetName()) + if existing { + log.Errorf("Auth Provider: %s already registered", c.GetName()) + } else { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.portalProxy.DoOidcFlowRequest, + UserInfo: nil, + }) + } } diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go index 9a32914bc9..e459d270c4 100644 --- a/src/jetstream/plugins/kubernetes/auth/types.go +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -17,6 +17,11 @@ type KubeAuthProvider interface { AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) + RegisterJetstreamAuthType(portal interfaces.PortalProxy) +} + +// KubeJetstreamAuthProvider is the optional interface that can be implemented if you want to control Jetstream Auth Registration +type KubeJetstreamAuthProvider interface { DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) } diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go index dcd73d2ca3..eaaf6c8a83 100644 --- a/src/jetstream/plugins/kubernetes/auth_providers.go +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -4,7 +4,6 @@ import ( "strings" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) var kubeAuthProviders map[string]auth.KubeAuthProvider @@ -22,12 +21,8 @@ func (c *KubernetesSpecification) AddAuthProvider(provider auth.KubeAuthProvider kubeAuthProviders[name] = provider - // Register auth type with Jetstream - c.portalProxy.AddAuthProvider(name, interfaces.AuthProvider{ - Handler: provider.DoFlowRequest, - UserInfo: provider.GetUserFromToken, - }) - + // Get the auth provider to register itself with Stratos, if needed + provider.RegisterJetstreamAuthType(c.portalProxy) } // GetAuthProvider gets a Kubernetes auth provider by key diff --git a/src/jetstream/repository/interfaces/portal_proxy.go b/src/jetstream/repository/interfaces/portal_proxy.go index f8ce8d86b4..9815f8f610 100644 --- a/src/jetstream/repository/interfaces/portal_proxy.go +++ b/src/jetstream/repository/interfaces/portal_proxy.go @@ -56,7 +56,7 @@ type PortalProxy interface { RefreshUAALogin(username, password string, store bool) error GetUserTokenInfo(tok string) (u *JWTUserTokenInfo, err error) GetUAAUser(userGUID string) (*ConnectedUser, error) - + // Proxy API requests ProxyRequest(c echo.Context, uri *url.URL) (map[string]*CNSIRequest, error) DoProxyRequest(requests []ProxyRequestInfo) (map[string]*CNSIRequest, error) @@ -68,9 +68,11 @@ type PortalProxy interface { AddAuthProvider(name string, provider AuthProvider) GetAuthProvider(name string) AuthProvider + HasAuthProvider(name string) bool DoAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request, authHandler AuthHandlerFunc) (*http.Response, error) OAuthHandlerFunc(cnsiRequest *CNSIRequest, req *http.Request, refreshOAuthTokenFunc RefreshOAuthTokenFunc) AuthHandlerFunc DoOAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) + DoOidcFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) GetCNSIUserFromOAuthToken(cnsiGUID string, cfTokenRecord *TokenRecord) (*ConnectedUser, bool) // Tokens - lower-level access @@ -79,8 +81,7 @@ type PortalProxy interface { AddLoginHook(priority int, function LoginHookFunc) error ExecuteLoginHooks(c echo.Context) error - + // Plugins GetPlugin(name string) interface{} - } diff --git a/src/jetstream/repository/interfaces/structs.go b/src/jetstream/repository/interfaces/structs.go index c3ba4fecfb..65783024e5 100644 --- a/src/jetstream/repository/interfaces/structs.go +++ b/src/jetstream/repository/interfaces/structs.go @@ -67,12 +67,10 @@ type ConnectedEndpoint struct { const ( // AuthTypeOAuth2 means OAuth2 AuthTypeOAuth2 = "OAuth2" - // AuthTypeOIDC means no OIDC + // AuthTypeOIDC means OIDC AuthTypeOIDC = "OIDC" // AuthTypeHttpBasic means HTTP Basic auth AuthTypeHttpBasic = "HttpBasic" - // AuthTypeAKS means AKS - AuthTypeAKS = "AKS" ) const ( From 8d256ce36d5226ff791c8cd58aa2fb9983589b9b Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 20 May 2019 15:48:56 +0100 Subject: [PATCH 33/34] Fix logging msg --- src/jetstream/plugins/kubernetes/auth/oidc.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index c61dd0ae16..70518c16b0 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -157,9 +157,7 @@ func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *h func (c *OIDCKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { // No need to register OIDC, as its already built in existing := c.portalProxy.HasAuthProvider(c.GetName()) - if existing { - log.Errorf("Auth Provider: %s already registered", c.GetName()) - } else { + if !existing { // Register auth type with Jetstream c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ Handler: c.portalProxy.DoOidcFlowRequest, From 257b81f972f563a717d36b0c5cbd2522b28953f9 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 21 Jun 2019 15:30:45 +0100 Subject: [PATCH 34/34] Fix firefox issues --- .../create-endpoint-connect.component.html | 5 +++-- .../create-endpoint-connect.component.scss | 2 +- .../src/shared/components/file-input/file-input.component.ts | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.html b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.html index 60cab3c3bb..0ca0904907 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.html +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.html @@ -6,9 +6,10 @@ Connect to {{ connectService?.config.name }} now (optional). -
    You may connect this endpoint at a later date from the - endpoints page.
    +
    You may connect this endpoint at a later date from the + endpoints page. +
    diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.scss b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.scss index 7cbbe92dd3..f8b7fcc146 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.scss +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.scss @@ -26,7 +26,7 @@ } &__subtext { font-size: 13px; - margin-top: -5px; + margin: 2px 0 0 24px; opacity: .5; position: absolute; } diff --git a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts index 9ac7452f36..7569c2e75e 100644 --- a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts +++ b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts @@ -58,7 +58,9 @@ export class FileInputComponent implements OnInit, OnDestroy { get fileCount(): number { return this.files && this.files.length || 0; } onNativeInputFileSelect($event) { - const fs = $event.srcElement.files; + // Ensure we work on Firefox as well as Chrome etc + const target = $event.target || $event.srcElement; + const fs = target.files; if (fs.length > 0) { this.files = fs; this.onFileSelect.emit(this.files[0]);