diff --git a/.github/workflows/webserver-unit-test.yaml b/.github/workflows/webserver-unit-test.yaml index aaf1c516..1b0d59de 100644 --- a/.github/workflows/webserver-unit-test.yaml +++ b/.github/workflows/webserver-unit-test.yaml @@ -46,7 +46,8 @@ jobs: run: apt-get install -y build-essential - name: Set up libpq run: apt-get install -y libpq5 - + - name: Install zstd + run: apt install zstd - name: Run tests working-directory: ./web/go/src env: @@ -78,7 +79,7 @@ jobs: AUTH0_PORTAL_RETURN_URL: 'https://portal.dymium.local:3001/app/logout' AUTH0_PORTAL_AUDIENCE: 'https://portal.dymium.local/api/handler' - MESH_PORT_RANGE=30000-30050 + MESH_PORT_RANGE: '30000-30050' SESSION_SECRET: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" CUSTOMER_HOST: 'portal.dymium.local' ADMIN_HOST: 'admin.dymium.local' diff --git a/.gitignore b/.gitignore index 1b4ac308..1b9abb98 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,6 @@ Tunnels/go/meshconnector/__debug_bin Tunnels/go/meshconnector/meshconnector Tunnels/go/meshserver/__debug_bin web/go/src/server +Tunnels/go/meshconnector/aws.sh +Tunnels/go/meshconnector/gcp.sh +Tunnels/go/meshconnector/runproderr.sh diff --git a/Tunnels/go/meshconnector/build.sh b/Tunnels/go/meshconnector/build.sh new file mode 100755 index 00000000..87a610e7 --- /dev/null +++ b/Tunnels/go/meshconnector/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +go build -a -tags netgo -ldflags '-X 'main.MajorVersion=0' -X 'main.MinorVersion=1' -w -extldflags "-static"' -o /tmp/meshconnector +tar -C /tmp -cvzf /tmp/meshconnector.tar.gz meshconnector +cp /tmp/meshconnector.tar.gz ../../../web/go/assets/customer//meshconnector_darwin_amd64.tar.gz +#aws s3 --profile dymium --region us-west-2 cp /tmp/meshconnector.tar.gz s3://dymium-connectors/macos/ +rm /tmp/meshconnector.tar.gz + + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -a -tags netgo -ldflags '-X 'main.MajorVersion=0' -X 'main.MinorVersion=1' -w -extldflags "-static"' -o /tmp/meshconnector +tar -C /tmp -cvzf /tmp/meshconnector.tar.gz meshconnector +cp /tmp/meshconnector.tar.gz ../../../web/go/assets/customer/meshconnector_linux_amd64.tar.gz +#aws s3 --profile dymium --region us-west-2 cp /tmp/meshconnector.tar.gz s3://dymium-connectors/linux/ +rm /tmp/meshconnector.tar.gz + +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ + go build -a -tags netgo -ldflags '-X 'main.MajorVersion=0' -X 'main.MinorVersion=1' -w -extldflags "-static"' -o /tmp/meshconnector.exe +zip /tmp/meshconnector.zip /tmp/meshconnector.exe +cp /tmp/meshconnector.zip ../../../web/go/assets/customer/meshconnector_windows_amd64.zip +#aws s3 --profile dymium --region us-west-2 cp /tmp/meshconnector.zip s3://dymium-connectors/windows/ +rm /tmp/meshconnector.zip + diff --git a/Tunnels/go/meshconnector/connector.go b/Tunnels/go/meshconnector/connector.go index 843a9e2a..5637b7ab 100644 --- a/Tunnels/go/meshconnector/connector.go +++ b/Tunnels/go/meshconnector/connector.go @@ -439,14 +439,14 @@ func DoConnect() { log.Errorf("Error connecting to %s: %s", portal, err.Error()) return } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if resp.StatusCode != 200 { - log.Errorf("Invalid response %d from %s", resp.StatusCode, portal) + log.Errorf("Invalid response %d from %s: %s", resp.StatusCode, portal, string(body)) return - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) if err != nil { log.Errorf("Error reading from %s: %s", portal, err.Error()) return @@ -474,6 +474,16 @@ func DoConnect() { log.Errorf("Error in X509KeyPair: %s", err) os.Exit(1) } + + c, e := x509.ParseCertificate([]byte(back.Certificate)) + if e == nil{ + log.Info("cert parsed") + for nm, _ := range c.DNSNames { + log.Infof("Tunnel: %s", nm) + } + } else { + log.Infof("error parsing %s\n%s", e.Error(), back.Certificate) + } tunnelserver := os.Getenv("TUNNELSERVER") diff --git a/web/go/src/authentication/authentication.go b/web/go/src/authentication/authentication.go index 83234675..7dd5c478 100644 --- a/web/go/src/authentication/authentication.go +++ b/web/go/src/authentication/authentication.go @@ -836,42 +836,44 @@ return "", err } func GetRoles(schema string, groups []string) []string { var roles []string - roles = append(roles, gotypes.RoleUser) - var count int log.Infof("GetRoles: groups: %v\n", groups) if(len(groups) == 0) { - - // let's see if mapping is present at all - sql := `select count(*) from ` + schema + `.groupmapping;`; - row := db.QueryRow(sql) - err := row.Scan(&count) - if(err != nil) { - log.Errorf("GetRoles error quering mapping: %s", err.Error()) - return roles; - } else { - if(count > 0) { - return roles - } - roles = append(roles, gotypes.RoleAdmin) - return roles - } + return roles; } - sql := `select count(*) from ` + schema + `.groupmapping where outergroup = any ($1) and adminaccess=true;`; + sql := `select outergroup,adminaccess from ` + schema + `.groupmapping where outergroup = any ($1);`; + log.Infof("GetRoles: sql: %s\n", sql) - row := db.QueryRow(sql, pq.Array(groups)) - err := row.Scan(&count) + rows, err := db.Query(sql, pq.Array(groups)) if(err != nil) { log.Errorf("GetRoles error quering mapping: %s", err.Error()) return roles; } else { - log.Infof("groups: %v, count: %d\n", groups, count) - if(count > 0) { - roles = append(roles, gotypes.RoleAdmin) + defer rows.Close() + var hasadmin, hasuser bool + + var group string + var admin bool + for rows.Next() { + err = rows.Scan(&group, &admin) + if(err != nil) { + log.Errorf("GetRoles error quering mapping: %s", err.Error()) + return roles; + } + if admin { + hasadmin = true + } + hasuser = true + } + if hasadmin { + roles = append(roles, gotypes.RoleAdmin) + } + if hasuser { + roles = append(roles, gotypes.RoleUser) } } + return roles - return roles; } func GeneratePortalJWT(picture string, schema string, name string, email string, groups []string, roles []string, org_id string) (string, error) { // generate JWT right header @@ -1661,6 +1663,22 @@ func GetFakeAuthentication () []byte{ `) } + +func CheckConnectorAuth(schema, key, secret string) error { + sql := `select accesssecret from ` + schema + `.connectorauth where accesskey=$1;` + + row := db.QueryRow(sql, key) + var realsecret string + err := row.Scan(&realsecret) + if err != nil { + return err + } + if secret != realsecret { + return errors.New("Invalid secret") + } + return nil +} + func GetTargets(schema, key, secret string, ) ([]string, error) { var targets []string sql := `select a.targetaddress, a.targetport, a.localport, b.id, a.id from ` + schema + `.connectors as a @@ -2046,17 +2064,16 @@ func GetConnectors(schema string) ( []types.Connector, error) { defer cancelfunc() tx, err := db.BeginTx(ctx, nil) - sql := `select a.id, a.name, a.accesskey, a.accesssecret, EXTRACT(epoch from (now() - a.createdat)), COALESCE(b.use_connector, false) from `+schema+`.connectorauth as a left join `+schema+`.connections as b on a.id=b.connector_id` - + sql := ` select a.id, a.name, a.accesskey, a.accesssecret, EXTRACT(epoch from (now() - a.createdat)), (select count(*) from `+schema+`.connections where connector_id=a.id) from `+schema+`.connectorauth as a;` rows, err := tx.QueryContext(ctx, sql) if nil == err { defer rows.Close() for rows.Next() { var id, name, accesskey, accesssecret string - var status bool + var nstatus int var age float64 - err = rows.Scan(&id, &name, &accesskey, &accesssecret, &age, &status) + err = rows.Scan(&id, &name, &accesskey, &accesssecret, &age, &nstatus) if err != nil { tx.Rollback() log.Errorf("GetConnectors error 0: %s", err.Error()) @@ -2072,7 +2089,7 @@ func GetConnectors(schema string) ( []types.Connector, error) { o.Secret = &accesssecret } var st string - if status { + if nstatus > 0 { st = "provisioned" } else { st = "configured" diff --git a/web/go/src/dhandlers/customerhandlers.go b/web/go/src/dhandlers/customerhandlers.go index abafe734..f4ee9a64 100644 --- a/web/go/src/dhandlers/customerhandlers.go +++ b/web/go/src/dhandlers/customerhandlers.go @@ -1370,11 +1370,17 @@ func GetConnectorCertificate(w http.ResponseWriter, r *http.Request) { key := t.Key secret := t.Secret + aerr := authentication.CheckConnectorAuth(schema, key, secret) + if aerr != nil { + http.Error(w, aerr.Error(), http.StatusInternalServerError) + return + } //fmt.Printf("schema: %s, key: %s, secret %s\n", schema, key, secret) if err != nil { log.ErrorTenantf(schema, "Api GetConnectorCertificate, error unmarshaling cert: %s", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) + return } pemBlock, _ := pem.Decode( []byte(t.Csr) ) @@ -1418,6 +1424,7 @@ func GetConnectorCertificate(w http.ResponseWriter, r *http.Request) { if err != nil { log.ErrorTenantf(schema, "Api GetConnectorCertificate, error: %s", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) + return } out := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: clientCRTRaw}) diff --git a/web/js/packages/admin/src/Error404.tsx b/web/js/packages/admin/src/Error404.tsx index cb5c47bb..309c2889 100644 --- a/web/js/packages/admin/src/Error404.tsx +++ b/web/js/packages/admin/src/Error404.tsx @@ -19,8 +19,8 @@ export default function Error404() { Dymium
-

Error 404.

-

Page Not Found.

+

Error 404

+

Page Not Found

The application is still under construction. Please be patient!
diff --git a/web/js/packages/common/App.scss b/web/js/packages/common/App.scss index 4dbf93ff..0eca459a 100644 --- a/web/js/packages/common/App.scss +++ b/web/js/packages/common/App.scss @@ -36,6 +36,12 @@ body, #root { color: white !important; text-shadow: 1px 1px 0 rgb(58 58 58 / 67%); } +.unauthorized { + font-family: "Roboto"; + text-transform: uppercase; + color: $main-yellow; + text-shadow: 1px 1px 0 rgb(58 58 58 / 67%); +} .logofooter { font-family: "Roboto"; color: white !important; diff --git a/web/js/packages/portal/src/App/AuthenticatedApp.tsx b/web/js/packages/portal/src/App/AuthenticatedApp.tsx index 84cc0008..aee20a3c 100644 --- a/web/js/packages/portal/src/App/AuthenticatedApp.tsx +++ b/web/js/packages/portal/src/App/AuthenticatedApp.tsx @@ -2,12 +2,45 @@ import React, { useEffect } from 'react'; import { Outlet } from "react-router-dom"; import Row from 'react-bootstrap/Row' import Col from 'react-bootstrap/Col' - +import Backdrop from "../Backdrop" import Menu from './Menu' import Auth from '../Auth' +import { Link } from "react-router-dom"; import Sidebar from './Sidebar' +import * as com from '../Common' const AuthenticatedApp = () => { + let roles = com.getTokenProperty("roles") + if (roles === null) { + return
+ +
+
+
+
+

+ Dymium

+
+
+

Error

+

Not authorized!

+
+ You are succesfully authenticated but not authorized to use Dymium. Please contact your administrator. +
+ +
+
+ +
+
+ + + +
+ } return ( <> diff --git a/web/js/packages/portal/src/App/Connectors.tsx b/web/js/packages/portal/src/App/Connectors.tsx index cb6cabb0..1a134031 100644 --- a/web/js/packages/portal/src/App/Connectors.tsx +++ b/web/js/packages/portal/src/App/Connectors.tsx @@ -35,6 +35,54 @@ const databases = Object.keys(com.databaseTypes).map(key => { }) +function Downloads(props) { + return
+ +
Connector Binary Downloads
+
+
For MS Windows:
+ Click to Download MS Windows Connector +
+ +
+ Usage:
+ >meshconnector.exe +
+ +
+
+
+
+
For x64 Linux:
+ Click to Download Linux Connector +
+ +
+ Usage:
+ >./meshconnector +
+ +
+
+
+ +
+
For Mac OS X:
+ Click to Download Linux Connector +
+ +
+ Usage:
+ >./meshconnector +
+ +
+
+
+ +
+ +} function ConnectionForm(props) { let regenerate = () => { @@ -118,9 +166,9 @@ function ConnectionForm(props) { -
Status:
-
{ props.tunnel[i].status !== "active" ? - : } { tun.humanReadableTunnelStatus(props.tunnel[i].status)}
+
Status:
+
{props.tunnel[i].status !== "active" ? + : } {tun.humanReadableTunnelStatus(props.tunnel[i].status)}
@@ -181,10 +229,10 @@ function ConnectionForm(props) { - + - - - + + + @@ -231,7 +279,7 @@ function ConnectionForm(props) { Type systemwide unique name to use in SQL - { (props.context === "edit" && props.secret[0] !== '*') && + {(props.context === "edit" && props.secret[0] !== '*') && } {props.context === "edit" && @@ -241,26 +289,26 @@ function ConnectionForm(props) { {props.context === "edit" && - - - - Connector ID: - + + + Connector ID: + + readOnly + value={props.id} - - - - + /> - } - { (props.context === "edit" && props.secret[0] !== '*') && -
{guidance}
+ + + + + + } + {(props.context === "edit" && props.secret[0] !== '*') && +
{guidance}
}
Configure Tunnels to Data Sources:
@@ -270,11 +318,11 @@ function ConnectionForm(props) { ) } -let getAccessKey = (setKey: React.Dispatch>, +let getAccessKey = (setKey: React.Dispatch>, setSecret: React.Dispatch>, - setSpinner: React.Dispatch>, + setSpinner: React.Dispatch>, setAlert: React.Dispatch> - ) => { +) => { setSpinner(true) http.sendToServer("GET", "/api/getaccesskey", null, "", @@ -332,7 +380,7 @@ export function AddConnector() { const [alert, setAlert] = useState(<>) useEffect(() => { - getAccessKey(setKey, setSecret,setSpinner, setAlert) + getAccessKey(setKey, setSecret, setSpinner, setAlert) }, []) const appDispatch = useAppDispatch() let sendConnector = () => { @@ -355,8 +403,8 @@ export function AddConnector() { setKey("") setSecret("") setTunnel([tun.Tunnel.fromJson({ id: "", name: "", address: "", port: "" })]) - - appDispatch(setSelectedConnectorDefault(js.token) ) + + appDispatch(setSelectedConnectorDefault(js.token)) appDispatch(setActiveConnectorTab("edit")) navigate('/app/connectors/redirect#bookmark') @@ -532,7 +580,7 @@ export function EditConnectors(props) { const defaultSorted = [{ dataField: 'name', order: 'asc' - }]; + }]; let columns = [ { dataField: 'id', @@ -552,7 +600,7 @@ export function EditConnectors(props) { return
{tun.humanReadableConnectorStatus(row["status"])}
}, headerStyle: { width: '200px' }, - }, + }, { text: 'Tunnels', dataField: 'tunnels', @@ -654,9 +702,9 @@ export function EditConnectors(props) { let body = { Id: selectedId, } - if(selectedId === rememberedSelection) { + if (selectedId === rememberedSelection) { appDispatch(setSelectedConnectorDefault("")) - } + } setSpinner(true) http.sendToServer("POST", "/api/deleteconnector", "", JSON.stringify(body), @@ -811,20 +859,20 @@ export function EditConnectors(props) { return state.reducer.activeConnectorTab } - ) + ) const refb = useRef(null); const history = useNavigate(); useEffect(() => { - - if (location.hash === '#bookmark') { - window.history.replaceState("", "Edit Connectors", '/app/connectors'); - setTimeout( () => { - if(refb.current != null) - refb.current.scrollIntoView({ behavior: "smooth", block: "end" }); - - }, 300 ) - } + + if (location.hash === '#bookmark') { + window.history.replaceState("", "Edit Connectors", '/app/connectors'); + setTimeout(() => { + if (refb.current != null) + refb.current.scrollIntoView({ behavior: "smooth", block: "end" }); + + }, 300) + } }, [t]) return ( @@ -870,7 +918,7 @@ export function EditConnectors(props) {
- getConnectors()} className="fa fa-refresh ablue cursor-pointer" style={{position: 'relative', top: '2px'}} aria-hidden="true"> + getConnectors()} className="fa fa-refresh ablue cursor-pointer" style={{ position: 'relative', top: '2px' }} aria-hidden="true">
@@ -889,7 +937,7 @@ export function EditConnectors(props) { }
} - { (location.hash === '#bookmark') && "fafafafa" } + {(location.hash === '#bookmark') && "fafafafa"} { rememberedSelection !== "" && displayConnector() } @@ -899,7 +947,7 @@ export function EditConnectors(props) { } function useQuery() { const { search } = useLocation(); - + return React.useMemo(() => new URLSearchParams(search), [search]); } function Connectors() { @@ -914,12 +962,12 @@ function Connectors() { let query = useQuery(); useEffect(() => { - if (location.pathname === '/app/connectors/redirect#bookmark') { - navigate('/app/connectors#bookmark') - } + if (location.pathname === '/app/connectors/redirect#bookmark') { + navigate('/app/connectors#bookmark') + } }, [t]) - + return ( + + + ) } diff --git a/web/js/packages/portal/src/App/Datascopes.tsx b/web/js/packages/portal/src/App/Datascopes.tsx index 53104867..c390ade7 100644 --- a/web/js/packages/portal/src/App/Datascopes.tsx +++ b/web/js/packages/portal/src/App/Datascopes.tsx @@ -77,7 +77,7 @@ export function AddDatascope(props) { if(js.status === "OK") { setAlert( setAlert(<>)} dismissible> - Data scope {dbname} created successfully! + Ghost Database {dbname} created successfully! ) } else { diff --git a/web/js/packages/portal/src/App/EditDatascopes.tsx b/web/js/packages/portal/src/App/EditDatascopes.tsx index d2ea3023..a9b42a84 100644 --- a/web/js/packages/portal/src/App/EditDatascopes.tsx +++ b/web/js/packages/portal/src/App/EditDatascopes.tsx @@ -251,7 +251,7 @@ export default function EditDatascopes() { if (js.status === "OK") { setAlert( setAlert(<>)} dismissible> - Data scope {dbname} updated successfully! + Ghost Database {dbname} updated successfully! ) capi.getConnections(setSpinner, setConns, setAlert, remap, () => { @@ -343,7 +343,7 @@ export default function EditDatascopes() { if (js.status === "OK") { setAlert( setAlert(<>)} dismissible> - Data scope {dbname} updated successfully! + Ghost Database {dbname} updated successfully! ) capi.getConnections(setSpinner, setConns, setAlert, remap, () => { diff --git a/web/js/packages/portal/src/App/Sidebar.tsx b/web/js/packages/portal/src/App/Sidebar.tsx index c02521cc..ddf38da3 100644 --- a/web/js/packages/portal/src/App/Sidebar.tsx +++ b/web/js/packages/portal/src/App/Sidebar.tsx @@ -15,8 +15,8 @@ export default function Sidebar() { }) const appDispatch = useAppDispatch() let roles = com.getTokenProperty("roles") - if (roles === undefined) { - roles = ["user"] + if (roles == null || roles.length == 0) { + return <> } let isadmin = roles.includes("admin") let isuser = roles.includes("user") diff --git a/web/js/packages/portal/src/App/TestSQL.tsx b/web/js/packages/portal/src/App/TestSQL.tsx index d87d0086..1473794d 100644 --- a/web/js/packages/portal/src/App/TestSQL.tsx +++ b/web/js/packages/portal/src/App/TestSQL.tsx @@ -308,7 +308,7 @@ function Test() { - Select Data Scope + Select Ghost Database { diff --git a/web/js/packages/portal/src/tests/__snapshots__/AddDatascope.test.js.snap b/web/js/packages/portal/src/tests/__snapshots__/AddDatascope.test.js.snap index 156ea46c..df9beffe 100644 --- a/web/js/packages/portal/src/tests/__snapshots__/AddDatascope.test.js.snap +++ b/web/js/packages/portal/src/tests/__snapshots__/AddDatascope.test.js.snap @@ -6569,7 +6569,7 @@ Object { Close alert - Data scope + Ghost Database testdb created successfully!
@@ -6918,7 +6918,7 @@ Object { Close alert - Data scope + Ghost Database testdb created successfully! diff --git a/web/js/packages/portal/src/tests/__snapshots__/Sidebar.test.js.snap b/web/js/packages/portal/src/tests/__snapshots__/Sidebar.test.js.snap index 59ec37b1..30b0f62a 100644 --- a/web/js/packages/portal/src/tests/__snapshots__/Sidebar.test.js.snap +++ b/web/js/packages/portal/src/tests/__snapshots__/Sidebar.test.js.snap @@ -1,59 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Instantiate sidebar 1`] = ` -