Merge pull request #4 from RafaySystems/init-saml

SAML based user authentication
This commit is contained in:
nirav-rafay
2022-01-24 19:56:22 +05:30
committed by GitHub
14 changed files with 1817 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
APP_HOST_HTTP="http://localhost:8000"
@@ -0,0 +1,881 @@
{
"swagger": "2.0",
"info": {
"title": "Identity Provider (IdP) Service",
"version": "2.0",
"contact": {
"name": "Rafay Dev"
}
},
"tags": [
{
"name": "Idp"
}
],
"schemes": [
"https"
],
"consumes": [
"application/json",
"application/yaml"
],
"produces": [
"application/json",
"application/yaml"
],
"paths": {
"/auth/v3/sso/idp": {
"get": {
"operationId": "Idp_ListIdps",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3IdpList"
}
},
"403": {
"description": "Returned when the user does not have permission to access the resource.",
"schema": {}
},
"404": {
"description": "Returned when the resource does not exist.",
"schema": {
"type": "string",
"format": "string"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/googlerpcStatus"
}
}
},
"tags": [
"Idp"
]
},
"post": {
"operationId": "Idp_CreateIdp",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3Idp"
}
},
"201": {
"description": "Returned when idp is created successfully.",
"schema": {}
},
"403": {
"description": "Returned when the user does not have permission to access the resource.",
"schema": {}
},
"404": {
"description": "Returned when the resource does not exist.",
"schema": {
"type": "string",
"format": "string"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/googlerpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v3Idp"
}
}
],
"tags": [
"Idp"
]
}
},
"/auth/v3/sso/idp/{metadata.id}": {
"get": {
"operationId": "Idp_GetIdp",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3Idp"
}
},
"403": {
"description": "Returned when the user does not have permission to access the resource.",
"schema": {}
},
"404": {
"description": "Returned when the resource does not exist.",
"schema": {
"type": "string",
"format": "string"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/googlerpcStatus"
}
}
},
"parameters": [
{
"name": "metadata.id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "apiVersion",
"description": "API Version. API Version of the idp resource",
"in": "query",
"required": false,
"type": "string",
"default": "usermgmt.k8smgmt.io/v3"
},
{
"name": "kind",
"description": "Kind. Kind of the idp resource",
"in": "query",
"required": false,
"type": "string",
"default": "Idp"
},
{
"name": "metadata.name",
"description": "Name. name of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.description",
"description": "Description. description of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.project",
"description": "Project. Project of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.organization",
"description": "Organization. Organization to which the resource belongs",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.partner",
"description": "Partner. Partner to which the resource belongs",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.modifiedAt",
"in": "query",
"required": false,
"type": "string",
"format": "date-time"
},
{
"name": "spec.idpName",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.domain",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.acsUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.ssoUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.idpCert",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.spCert",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.metadataUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.metadataFilename",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.saeEnabled",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "spec.groupAttributeName",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.nameIdFormat",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.consumerBinding",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.spEntityId",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "status.conditionType",
"description": "Condition Type. type of the status condition",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "status.conditionStatus",
"description": "Condition Status. status of the condition",
"in": "query",
"required": false,
"type": "string",
"enum": [
"StatusNotSet",
"StatusSubmitted",
"StatusOK",
"StatusFailed"
],
"default": "StatusNotSet"
},
{
"name": "status.lastUpdated",
"description": "Last Updated. when the condition status is last updated",
"in": "query",
"required": false,
"type": "string",
"format": "date-time"
},
{
"name": "status.reason",
"description": "Reason. reason of the last condition status",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"Idp"
]
},
"delete": {
"operationId": "Idp_DeleteIdp",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"properties": {}
}
},
"204": {
"description": "Returned when idp is deleted successfully.",
"schema": {}
},
"403": {
"description": "Returned when the user does not have permission to access the resource.",
"schema": {}
},
"404": {
"description": "Returned when the resource does not exist.",
"schema": {
"type": "string",
"format": "string"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/googlerpcStatus"
}
}
},
"parameters": [
{
"name": "metadata.id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "apiVersion",
"description": "API Version. API Version of the idp resource",
"in": "query",
"required": false,
"type": "string",
"default": "usermgmt.k8smgmt.io/v3"
},
{
"name": "kind",
"description": "Kind. Kind of the idp resource",
"in": "query",
"required": false,
"type": "string",
"default": "Idp"
},
{
"name": "metadata.name",
"description": "Name. name of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.description",
"description": "Description. description of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.project",
"description": "Project. Project of the resource",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.organization",
"description": "Organization. Organization to which the resource belongs",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.partner",
"description": "Partner. Partner to which the resource belongs",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "metadata.modifiedAt",
"in": "query",
"required": false,
"type": "string",
"format": "date-time"
},
{
"name": "spec.idpName",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.domain",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.acsUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.ssoUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.idpCert",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.spCert",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.metadataUrl",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.metadataFilename",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.saeEnabled",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "spec.groupAttributeName",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.nameIdFormat",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.consumerBinding",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "spec.spEntityId",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "status.conditionType",
"description": "Condition Type. type of the status condition",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "status.conditionStatus",
"description": "Condition Status. status of the condition",
"in": "query",
"required": false,
"type": "string",
"enum": [
"StatusNotSet",
"StatusSubmitted",
"StatusOK",
"StatusFailed"
],
"default": "StatusNotSet"
},
{
"name": "status.lastUpdated",
"description": "Last Updated. when the condition status is last updated",
"in": "query",
"required": false,
"type": "string",
"format": "date-time"
},
{
"name": "status.reason",
"description": "Reason. reason of the last condition status",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"Idp"
]
},
"put": {
"operationId": "Idp_UpdateIdp",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3Idp"
}
},
"403": {
"description": "Returned when the user does not have permission to access the resource.",
"schema": {}
},
"404": {
"description": "Returned when the resource does not exist.",
"schema": {
"type": "string",
"format": "string"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/googlerpcStatus"
}
}
},
"parameters": [
{
"name": "metadata.id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v3Idp"
}
}
],
"tags": [
"Idp"
]
}
}
},
"definitions": {
"commonv3Status": {
"type": "object",
"properties": {
"conditionType": {
"type": "string",
"description": "type of the status condition",
"title": "Condition Type",
"readOnly": true
},
"conditionStatus": {
"$ref": "#/definitions/v3ConditionStatus",
"enum": [
"StatusNotSet",
"StatusSubmitted",
"StatusOK",
"StatusFailed"
],
"description": "status of the condition",
"title": "Condition Status",
"readOnly": true
},
"lastUpdated": {
"type": "string",
"format": "date-time",
"description": "when the condition status is last updated",
"title": "Last Updated",
"readOnly": true
},
"reason": {
"type": "string",
"description": "reason of the last condition status",
"title": "Reason",
"readOnly": true
}
},
"description": "status of a resource",
"title": "Status",
"readOnly": true
},
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"v3ConditionStatus": {
"type": "string",
"enum": [
"StatusNotSet",
"StatusSubmitted",
"StatusOK",
"StatusFailed"
],
"default": "StatusNotSet",
"title": "$title: ConditionStatus\n$description: status of a condition for a resource"
},
"v3Idp": {
"type": "object",
"properties": {
"apiVersion": {
"type": "string",
"default": "usermgmt.k8smgmt.io/v3",
"description": "API Version of the idp resource",
"title": "API Version"
},
"kind": {
"type": "string",
"default": "Idp",
"description": "Kind of the idp resource",
"title": "Kind"
},
"metadata": {
"$ref": "#/definitions/v3Metadata",
"description": "Metadata of the idp resource",
"title": "Metadata"
},
"spec": {
"$ref": "#/definitions/v3IdpSpec",
"description": "Spec of the idp resource",
"title": "Spec"
},
"status": {
"$ref": "#/definitions/commonv3Status",
"description": "Status of the resource",
"title": "Status",
"readOnly": true
}
},
"description": "Idp",
"title": "Idp",
"required": [
"apiVersion",
"kind",
"metadata",
"spec"
]
},
"v3IdpList": {
"type": "object",
"properties": {
"apiVersion": {
"type": "string",
"default": "usermgmt.k8smgmt.io/v3",
"description": "API Version of the idp list resource",
"title": "API Version",
"readOnly": true
},
"kind": {
"type": "string",
"default": "IdpList",
"description": "Kind of the idp list resource",
"title": "Kind",
"readOnly": true
},
"metadata": {
"$ref": "#/definitions/v3ListMetadata",
"description": "Metadata of the idp list resource",
"title": "Metadata",
"readOnly": true
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/v3Idp"
},
"description": "List of the idp resources",
"title": "Items",
"readOnly": true
}
},
"description": "idp list",
"title": "IdpList",
"readOnly": true
},
"v3IdpSpec": {
"type": "object",
"properties": {
"idpName": {
"type": "string"
},
"domain": {
"type": "string"
},
"acsUrl": {
"type": "string"
},
"ssoUrl": {
"type": "string"
},
"idpCert": {
"type": "string"
},
"spCert": {
"type": "string"
},
"metadataUrl": {
"type": "string"
},
"metadataFilename": {
"type": "string"
},
"saeEnabled": {
"type": "boolean"
},
"groupAttributeName": {
"type": "string"
},
"nameIdFormat": {
"type": "string"
},
"consumerBinding": {
"type": "string"
},
"spEntityId": {
"type": "string"
}
}
},
"v3ListMetadata": {
"type": "object",
"properties": {
"count": {
"type": "string",
"format": "int64"
},
"offset": {
"type": "string",
"format": "int64"
},
"limt": {
"type": "string",
"format": "int64"
}
},
"title": "$title: ListMetadata\n$description: metadata for a list of resources\n$required: enabled"
},
"v3Metadata": {
"type": "object",
"example": {
"name": "some-name",
"project": "defaultproject"
},
"properties": {
"name": {
"type": "string",
"description": "name of the resource",
"title": "Name"
},
"description": {
"type": "string",
"description": "description of the resource",
"title": "Description"
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "labels of the resource",
"title": "Lables"
},
"annotations": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "annotations of the resource",
"title": "Annotations"
},
"project": {
"type": "string",
"description": "Project of the resource",
"title": "Project"
},
"organization": {
"type": "string",
"description": "Organization to which the resource belongs",
"title": "Organization"
},
"partner": {
"type": "string",
"description": "Partner to which the resource belongs",
"title": "Partner"
},
"id": {
"type": "string",
"readOnly": true
},
"modifiedAt": {
"type": "string",
"format": "date-time",
"readOnly": true
}
},
"description": "metadata of the resource",
"title": "Metadata",
"required": [
"name",
"project"
]
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "X-RAFAY-API-KEYID",
"in": "header"
},
"BasicAuth": {
"type": "basic"
}
},
"security": [
{
"ApiKeyAuth": [],
"BasicAuth": []
}
]
}
@@ -0,0 +1,43 @@
{
"swagger": "2.0",
"info": {
"title": "proto/types/userpb/v3/idp.proto",
"version": "version not set"
},
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}
+8
View File
@@ -5,6 +5,7 @@ go 1.17
require (
github.com/RafaySystems/rcloud-base/components/adminsrv v0.0.0-unpublished
github.com/RafaySystems/rcloud-base/components/common v0.0.0-unpublished
github.com/crewjam/saml v0.4.6
github.com/gogo/protobuf v1.3.2
github.com/google/uuid v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2
@@ -21,20 +22,27 @@ require (
)
require (
github.com/beevik/etree v1.1.0 // indirect
github.com/crewjam/httperr v0.2.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/processout/grpc-go-pool v1.2.1 // indirect
github.com/russellhaering/goxmldsig v1.1.1 // indirect
github.com/segmentio/asm v1.1.0 // indirect
github.com/segmentio/encoding v0.3.2 // indirect
github.com/speps/go-hashids v2.0.0+incompatible // indirect
+27 -2
View File
@@ -76,6 +76,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -122,9 +124,14 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo=
github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
github.com/crewjam/saml v0.4.6 h1:XCUFPkQSJLvzyl4cW9OvpWUbRf0gE7VUpU8ZnilbeM4=
github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@@ -186,6 +193,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@@ -328,6 +337,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -354,6 +364,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -365,6 +378,8 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -407,7 +422,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -435,6 +449,7 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -474,6 +489,11 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM=
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
@@ -552,6 +572,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@@ -609,6 +630,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -1057,8 +1079,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@@ -1085,6 +1108,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+5
View File
@@ -61,6 +61,7 @@ var (
us service.UserService
gs service.GroupService
rs service.RoleService
is service.IdpService
dev bool
_log = logv2.GetLogger()
authPool authv3.AuthPool
@@ -122,6 +123,7 @@ func setup() {
us = service.NewUserService(kc, db)
gs = service.NewGroupService(db)
rs = service.NewRoleService(db)
is = service.NewIdpService(db)
_log.Infow("usermgmt setup complete")
}
@@ -155,6 +157,7 @@ func runAPI(wg *sync.WaitGroup, ctx context.Context) {
pbrpcv3.RegisterUserHandlerFromEndpoint,
pbrpcv3.RegisterGroupHandlerFromEndpoint,
pbrpcv3.RegisterRoleHandlerFromEndpoint,
pbrpcv3.RegisterIdpHandlerFromEndpoint,
)
if err != nil {
_log.Fatalw("unable to create gateway", "error", err)
@@ -188,6 +191,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) {
userServer := server.NewUserServer(us)
groupServer := server.NewGroupServer(gs)
roleServer := server.NewRoleServer(rs)
idpServer := server.NewIdpServer(is)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", rpcPort))
if err != nil {
@@ -224,6 +228,7 @@ func runRPC(wg *sync.WaitGroup, ctx context.Context) {
rpcv3.RegisterUserServer(s, userServer)
rpcv3.RegisterGroupServer(s, groupServer)
rpcv3.RegisterRoleServer(s, roleServer)
rpcv3.RegisterIdpServer(s, idpServer)
_log.Infow("starting rpc server", "port", rpcPort)
err = s.Serve(l)
@@ -0,0 +1,34 @@
package models
import (
"time"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
type Idp struct {
bun.BaseModel `bun:"table:authsrv_idp,alias:idp"`
Id uuid.UUID `bun:"id,type:uuid,pk,default:uuid_generate_v4()"`
Name string `bun:"name,notnull,unique"`
Description string `bun:"description"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"`
ModifiedAt time.Time `bun:"modified_at,notnull,default:current_timestamp"`
IdpName string `bun:"idp_name,notnull"`
Domain string `bun:"domain,notnull,unique"`
AcsURL string `bun:"acs_url,notnull,unique"`
OrganizationId uuid.UUID `bun:"organization_id,type:uuid"`
PartnerId uuid.UUID `bun:"partner_id,type:uuid"`
SsoURL string `bun:"sso_url"`
IdpCert string `bun:"idp_cert"`
SpCert string `bun:"sp_cert"`
SpKey string `bun:"sp_key"`
MetadataURL string `bun:"metadata_url"`
MetadataFilename string `bun:"metadata_filename"`
Metadata []byte `bun:"metadata"`
GroupAttributeName string `bun:"group_attribute_name"`
SaeEnabled bool `bun:"is_sae_enabled"`
Trash bool `bun:"trash,default:false"`
}
+38
View File
@@ -0,0 +1,38 @@
package server
import (
"context"
"github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/service"
rpcv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/rpc/v3"
userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3"
"google.golang.org/protobuf/types/known/emptypb"
)
type idpServer struct {
service.IdpService
}
func NewIdpServer(is service.IdpService) rpcv3.IdpServer {
return &idpServer{is}
}
func (s *idpServer) CreateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
return s.IdpService.Create(ctx, idp)
}
func (s *idpServer) GetIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
return s.IdpService.GetByID(ctx, idp)
}
func (s *idpServer) ListIdps(ctx context.Context, _ *emptypb.Empty) (*userv3.IdpList, error) {
return s.IdpService.List(ctx)
}
func (s *idpServer) UpdateIdp(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
return s.IdpService.Update(ctx, idp)
}
func (s *idpServer) DeleteIdp(ctx context.Context, idpID *userv3.Idp) (*emptypb.Empty, error) {
return &emptypb.Empty{}, s.IdpService.Delete(ctx, idpID)
}
+385
View File
@@ -0,0 +1,385 @@
package service
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net/url"
"os"
"time"
"github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg"
commonv3 "github.com/RafaySystems/rcloud-base/components/common/proto/types/commonpb/v3"
"github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/internal/models"
userv3 "github.com/RafaySystems/rcloud-base/components/usermgmt/proto/types/userpb/v3"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
var baseUrl *url.URL
func init() {
base, ok := os.LookupEnv("APP_HOST_HTTP")
if !ok || len(base) == 0 {
panic("APP_HOST_HTTP env not set")
}
var err error
baseUrl, err = url.Parse(base)
if err != nil {
panic("Failed to get application url")
}
}
type IdpService interface {
Create(context.Context, *userv3.Idp) (*userv3.Idp, error)
GetByID(context.Context, *userv3.Idp) (*userv3.Idp, error)
List(context.Context) (*userv3.IdpList, error)
Update(context.Context, *userv3.Idp) (*userv3.Idp, error)
Delete(context.Context, *userv3.Idp) error
}
type idpService struct {
dao pg.EntityDAO
}
func NewIdpService(db *bun.DB) IdpService {
return &idpService{
dao: pg.NewEntityDAO(db),
}
}
func generateAcsURL() (string, error) {
uuid := uuid.New()
return fmt.Sprintf("%s/%s/", baseUrl.String(), uuid.String()), nil
}
// generateSpCert generates self signed certificate. Returns cert and
// private key.
func generateSpCert(host string) (string, string, error) {
// generate private key of type rsa
priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return "", "", err
}
privPEM := new(bytes.Buffer)
err = pem.Encode(privPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
})
if err != nil {
return "", "", err
}
privPEMBytes, err := ioutil.ReadAll(privPEM)
if err != nil {
return "", "", err
}
template := &x509.Certificate{
SerialNumber: big.NewInt(1000),
Subject: pkix.Name{
Organization: []string{"Rafay"},
Country: []string{"US"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(30, 0, 0),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
DNSNames: []string{host},
}
// generate self sign certificate
cBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
return "", "", err
}
cPEM := new(bytes.Buffer)
err = pem.Encode(cPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: cBytes,
})
if err != nil {
return "", "", err
}
cPEMBytes, err := ioutil.ReadAll(cPEM)
if err != nil {
return "", "", err
}
return string(cPEMBytes), string(privPEMBytes), nil
}
func (s *idpService) Create(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
name := idp.Metadata.GetName()
domain := idp.Spec.GetDomain()
// validate name and domain
if len(name) == 0 {
return &userv3.Idp{}, fmt.Errorf("EMPTY NAME")
}
if len(domain) == 0 {
return &userv3.Idp{}, fmt.Errorf("EMPTY DOMAIN")
}
e := &models.Idp{}
s.dao.GetByName(ctx, name, e)
if e.Name == name {
return &userv3.Idp{}, fmt.Errorf("DUPLICATE NAME")
}
s.dao.GetX(ctx, "domain", domain, e)
if e.Domain == domain {
return &userv3.Idp{}, fmt.Errorf("DUPLICATE DOMAIN")
}
acsURL, err := generateAcsURL()
if err != nil {
return &userv3.Idp{}, err
}
entity := &models.Idp{
Name: name,
Description: idp.Metadata.GetDescription(),
CreatedAt: time.Now(),
IdpName: idp.Spec.GetIdpName(),
Domain: domain,
AcsURL: acsURL,
SsoURL: idp.Spec.GetSsoUrl(),
IdpCert: idp.Spec.GetIdpCert(),
MetadataURL: idp.Spec.GetMetadataUrl(),
MetadataFilename: idp.Spec.GetMetadataFilename(),
GroupAttributeName: idp.Spec.GetGroupAttributeName(),
SaeEnabled: idp.Spec.GetSaeEnabled(),
}
if entity.SaeEnabled {
spcert, spkey, err := generateSpCert(baseUrl.Host)
if err != nil {
return &userv3.Idp{}, err
}
entity.SpCert = spcert
entity.SpKey = spkey
}
_, err = s.dao.Create(ctx, entity)
if err != nil {
return &userv3.Idp{}, err
}
rv := &userv3.Idp{
ApiVersion: "usermgmt.k8smgmt.io/v3",
Kind: "Idp",
Metadata: &commonv3.Metadata{
Name: entity.Name,
Organization: entity.OrganizationId.String(),
Partner: entity.PartnerId.String(),
Id: entity.Id.String(),
},
Spec: &userv3.IdpSpec{
IdpName: entity.IdpName,
Domain: entity.Domain,
AcsUrl: entity.AcsURL,
SsoUrl: entity.SsoURL,
IdpCert: entity.IdpCert,
SpCert: entity.SpCert,
MetadataUrl: entity.MetadataURL,
MetadataFilename: entity.MetadataFilename,
SaeEnabled: entity.SaeEnabled,
GroupAttributeName: entity.GroupAttributeName,
NameIdFormat: "Email Address",
ConsumerBinding: "HTTP-POST",
SpEntityId: entity.AcsURL,
},
}
return rv, nil
}
func (s *idpService) GetByID(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
id, err := uuid.Parse(idp.Metadata.GetId())
if err != nil {
return &userv3.Idp{}, err
}
entity := &models.Idp{}
// TODO: Check for existance of id before GetByID
_, err = s.dao.GetByID(ctx, id, entity)
if err != nil {
return &userv3.Idp{}, err
}
rv := &userv3.Idp{
ApiVersion: "usermgmt.k8smgmt.io/v3",
Kind: "Idp",
Metadata: &commonv3.Metadata{
Name: entity.Name,
Organization: entity.OrganizationId.String(),
Partner: entity.PartnerId.String(),
Id: entity.Id.String(),
},
Spec: &userv3.IdpSpec{
IdpName: entity.IdpName,
Domain: entity.Domain,
AcsUrl: entity.AcsURL,
SsoUrl: entity.SsoURL,
IdpCert: entity.IdpCert,
SpCert: entity.SpCert,
MetadataUrl: entity.MetadataURL,
MetadataFilename: entity.MetadataFilename,
SaeEnabled: entity.SaeEnabled,
GroupAttributeName: entity.GroupAttributeName,
NameIdFormat: "Email Address",
ConsumerBinding: "HTTP-POST",
SpEntityId: entity.AcsURL,
},
}
return rv, nil
}
func (s *idpService) Update(ctx context.Context, idp *userv3.Idp) (*userv3.Idp, error) {
var id, orgId, partId uuid.UUID
id, err := uuid.Parse(idp.Metadata.GetId())
// TODO: 400 Bad Request
if err != nil {
return &userv3.Idp{}, err
}
if len(idp.Metadata.GetOrganization()) != 0 {
orgId, err = uuid.Parse(idp.Metadata.GetOrganization())
if err != nil {
return &userv3.Idp{}, err
}
}
if len(idp.Metadata.GetPartner()) != 0 {
partId, err = uuid.Parse(idp.Metadata.GetPartner())
if err != nil {
return &userv3.Idp{}, err
}
}
_, err = s.dao.GetByID(ctx, id, &models.Idp{})
// TODO: Return proper error for Id not exist
if err != nil {
return &userv3.Idp{}, err
}
entity := &models.Idp{
Id: id,
Name: idp.Metadata.GetName(),
Description: idp.Metadata.GetDescription(),
ModifiedAt: time.Now(),
IdpName: idp.Spec.GetIdpName(),
Domain: idp.Spec.GetDomain(),
AcsURL: idp.Spec.GetAcsUrl(),
OrganizationId: orgId,
PartnerId: partId,
SsoURL: idp.Spec.GetSsoUrl(),
IdpCert: idp.Spec.GetIdpCert(),
MetadataURL: idp.Spec.GetMetadataUrl(),
MetadataFilename: idp.Spec.GetMetadataFilename(),
GroupAttributeName: idp.Spec.GetGroupAttributeName(),
SaeEnabled: idp.Spec.GetSaeEnabled(),
}
if entity.SaeEnabled {
spcert, spkey, err := generateSpCert(baseUrl.Host)
if err != nil {
return &userv3.Idp{}, err
}
entity.SpCert = spcert
entity.SpKey = spkey
}
_, err = s.dao.Update(ctx, id, entity)
if err != nil {
return &userv3.Idp{}, err
}
rv := &userv3.Idp{
ApiVersion: "usermgmt.k8smgmt.io/v3",
Kind: "Idp",
Metadata: &commonv3.Metadata{
Name: entity.Name,
Organization: entity.OrganizationId.String(),
Partner: entity.PartnerId.String(),
Id: entity.Id.String(),
},
Spec: &userv3.IdpSpec{
IdpName: entity.IdpName,
Domain: entity.Domain,
AcsUrl: entity.AcsURL,
SsoUrl: entity.SsoURL,
IdpCert: entity.IdpCert,
SpCert: entity.SpCert,
MetadataUrl: entity.MetadataURL,
MetadataFilename: entity.MetadataFilename,
SaeEnabled: entity.SaeEnabled,
GroupAttributeName: entity.GroupAttributeName,
NameIdFormat: "Email Address",
ConsumerBinding: "HTTP-POST",
SpEntityId: entity.AcsURL,
},
}
return rv, nil
}
func (s *idpService) List(ctx context.Context) (*userv3.IdpList, error) {
var (
entities []models.Idp
orgID uuid.NullUUID
parID uuid.NullUUID
)
_, err := s.dao.List(ctx, parID, orgID, &entities)
if err != nil {
return &userv3.IdpList{}, err
}
// Get idps only till limit
var result []*userv3.Idp
for _, entity := range entities {
e := &userv3.Idp{
ApiVersion: "usermgmt.k8smgmt.io/v3",
Kind: "Idp",
Metadata: &commonv3.Metadata{
Name: entity.Name,
Organization: entity.OrganizationId.String(),
Partner: entity.PartnerId.String(),
Id: entity.Id.String(),
},
Spec: &userv3.IdpSpec{
IdpName: entity.IdpName,
Domain: entity.Domain,
AcsUrl: entity.AcsURL,
SsoUrl: entity.SsoURL,
IdpCert: entity.IdpCert,
SpCert: entity.SpCert,
MetadataUrl: entity.MetadataURL,
MetadataFilename: entity.MetadataFilename,
SaeEnabled: entity.SaeEnabled,
GroupAttributeName: entity.GroupAttributeName,
NameIdFormat: "Email Address",
ConsumerBinding: "HTTP-POST",
SpEntityId: entity.AcsURL,
},
}
result = append(result, e)
}
rv := &userv3.IdpList{
ApiVersion: "usermgmt.k8smgmt.io/v3",
Kind: "IdpList",
Items: result,
}
return rv, nil
}
func (s *idpService) Delete(ctx context.Context, idp *userv3.Idp) error {
id, err := uuid.Parse(idp.Metadata.GetId())
if err != nil {
return err
}
entity := &models.Idp{}
_, err = s.dao.GetByID(ctx, id, entity)
if entity.Id != id {
return fmt.Errorf("ID DOES NOT EXISTS")
}
err = s.dao.Delete(ctx, id, &models.Idp{})
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,167 @@
package saml
import (
"context"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"net/http"
"net/url"
"os"
"strings"
"github.com/RafaySystems/rcloud-base/components/usermgmt/pkg/internal/models"
"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
)
func newSAMLMiddlewareFromIDP(idp models.Idp) (*SAMLMiddleware, error) {
rootURL, err := url.Parse(os.Getenv("APP_HOST_HTTP"))
if err != nil {
return nil, err
}
var idpMetadata *saml.EntityDescriptor
if len(idp.Metadata) == 0 {
idpMetadataURL, err := url.Parse(idp.MetadataURL)
if err != nil {
return nil, err
}
idpMetadata, err = samlsp.FetchMetadata(context.Background(), http.DefaultClient,
*idpMetadataURL)
} else {
idpMetadata, err = samlsp.ParseMetadata(idp.Metadata)
if err != nil {
return nil, err
}
}
acsURL, err := url.Parse(idp.AcsURL)
if err != nil {
return nil, err
}
keyPair, err := tls.X509KeyPair([]byte(idp.SpCert), []byte(idp.SpKey))
if err != nil {
return nil, err
}
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
if err != nil {
return nil, err
}
opts := samlsp.Options{
EntityID: "",
URL: *rootURL,
Key: keyPair.PrivateKey.(*rsa.PrivateKey),
Certificate: keyPair.Leaf,
AllowIDPInitiated: false,
DefaultRedirectURI: "/prelogin",
IDPMetadata: idpMetadata,
SignRequest: false,
}
sp := samlsp.DefaultServiceProvider(opts)
sp.AcsURL = *acsURL
m := &samlsp.Middleware{
ServiceProvider: sp,
Binding: "",
ResponseBinding: saml.HTTPPostBinding,
OnError: samlsp.DefaultOnError,
Session: samlsp.DefaultSessionProvider(opts),
}
m.RequestTracker = samlsp.DefaultRequestTracker(opts, &m.ServiceProvider)
if opts.UseArtifactResponse {
m.ResponseBinding = saml.HTTPArtifactBinding
}
return &SAMLMiddleware{m}, nil
}
// SAMLAuth is an authentication middleware.
func (s *SAMLService) SAMLAuth(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, "failed to parse form data", http.StatusBadRequest)
return
}
username := r.PostForm.Get("username")
if !strings.Contains(username, "@") {
http.Error(w, "Invalid email address", http.StatusBadRequest)
return
}
domain := strings.SplitN(username, "@", 2)[1]
entity, err := s.EntityDAO.GetX(context.Background(), "domain", domain, &models.Idp{})
if err != nil {
http.Error(w, "No idp found for domain", http.StatusInternalServerError)
return
}
idp, ok := entity.(models.Idp)
if !ok {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
m, err := newSAMLMiddlewareFromIDP(idp)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
session, err := m.Session.GetSession(r)
if session != nil {
r = r.WithContext(samlsp.ContextWithSession(r.Context(), session))
w.Write([]byte("authentiated successfully"))
return
}
if err == samlsp.ErrNoSession {
m.HandleStartAuthFlow(w, r)
return
} else {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
}
// ServeACS performs SAML Response assertions.
func (s *SAMLService) ServeACS(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
base, _ := url.Parse(os.Getenv("APP_HOST_HTTP"))
acsURL := base.ResolveReference(r.URL)
entity, err := s.EntityDAO.GetX(context.Background(), "acs_url", acsURL.String(), &models.Idp{})
if err != nil {
http.Error(w, "No Idp for ACS URL", http.StatusInternalServerError)
return
}
idp, ok := entity.(models.Idp)
if !ok {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
m, err := newSAMLMiddlewareFromIDP(idp)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
possibleRequestIDs := []string{}
if m.ServiceProvider.AllowIDPInitiated {
possibleRequestIDs = append(possibleRequestIDs, "")
}
trackedRequests := m.RequestTracker.GetTrackedRequests(r)
for _, tr := range trackedRequests {
possibleRequestIDs = append(possibleRequestIDs, tr.SAMLRequestID)
}
assertion, err := m.ServiceProvider.ParseResponse(r, possibleRequestIDs)
if err != nil {
m.OnError(w, r, err)
return
}
m.CreateSessionFromAssertion(w, r, assertion, m.ServiceProvider.DefaultRedirectURI)
return
}
+21
View File
@@ -0,0 +1,21 @@
package saml
import (
pg "github.com/RafaySystems/rcloud-base/components/common/pkg/persistence/provider/pg"
"github.com/crewjam/saml/samlsp"
"github.com/uptrace/bun"
)
type SAMLMiddleware struct {
*samlsp.Middleware
}
type SAMLService struct {
EntityDAO pg.EntityDAO
}
func NewSAMLService(db *bun.DB) *SAMLService {
return &SAMLService{
EntityDAO: pg.NewEntityDAO(db),
}
}
@@ -0,0 +1 @@
package saml
+109
View File
@@ -0,0 +1,109 @@
syntax = "proto3";
package rafay.dev.rpc.v3;
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "proto/types/userpb/v3/idp.proto";
import "google/protobuf/empty.proto";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info : {
title : "Identity Provider (IdP) Service"
version : "2.0"
contact : {name : "Rafay Dev"}
}
schemes : HTTPS
consumes : "application/json"
consumes : "application/yaml"
produces : "application/json"
produces : "application/yaml"
security_definitions : {
security : {
key : "BasicAuth"
value : {type : TYPE_BASIC}
}
security : {
key : "ApiKeyAuth"
value : {type : TYPE_API_KEY in : IN_HEADER name : "X-RAFAY-API-KEYID"}
}
}
security : {
security_requirement : {
key : "BasicAuth"
value : {}
}
security_requirement : {
key : "ApiKeyAuth"
value : {}
}
}
responses : {
key : "403"
value : {
description : "Returned when the user does not have permission to access "
"the resource."
}
}
responses : {
key : "404"
value : {
description : "Returned when the resource does not exist."
schema : {json_schema : {type : STRING}}
}
}
};
service Idp {
rpc CreateIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) {
option (google.api.http) = {
post : "/auth/v3/sso/idp"
body : "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses : {
key : "201"
value : {description : "Returned when idp is created successfully."}
}
};
};
rpc GetIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) {
option (google.api.http) = {
get : "/auth/v3/sso/idp/{metadata.id}"
};
};
rpc ListIdps(google.protobuf.Empty) returns (rafay.dev.types.user.v3.IdpList) {
option (google.api.http) = {
get : "/auth/v3/sso/idp"
};
};
rpc UpdateIdp(rafay.dev.types.user.v3.Idp) returns (rafay.dev.types.user.v3.Idp) {
option (google.api.http) = {
put : "/auth/v3/sso/idp/{metadata.id}"
body : "*"
};
};
rpc DeleteIdp(rafay.dev.types.user.v3.Idp) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete : "/auth/v3/sso/idp/{metadata.id}"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses : {
key : "204"
value : {
description : "Returned when idp is deleted successfully."
}
}
};
};
// TODO: endpooint /auth/v1/sso/idp/{id}/upload_metadata/
// file content as request payload and response is Idp
}
@@ -0,0 +1,97 @@
syntax = "proto3";
package rafay.dev.types.user.v3;
import "proto/types/commonpb/v3/common.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
message Idp {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema : {
title : "Idp"
description : "Idp"
required : [ "apiVersion", "kind", "metadata", "spec" ]
}
};
string apiVersion = 1
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "API Version",
description : "API Version of the idp resource"
default : "usermgmt.k8smgmt.io/v3"
} ];
string kind = 2
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Kind",
description : "Kind of the idp resource"
default : "Idp"
} ];
rafay.dev.types.common.v3.Metadata metadata = 3
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Metadata",
description : "Metadata of the idp resource"
} ];
IdpSpec spec = 4
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Spec",
description : "Spec of the idp resource"
} ];
rafay.dev.types.common.v3.Status status = 5
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Status",
description : "Status of the resource"
read_only : true
} ];
}
message IdpSpec {
string idpName = 1;
string domain = 2;
string acsUrl = 3;
string ssoUrl = 4;
string idpCert = 5;
string spCert = 6;
string metadataUrl = 7;
string metadataFilename = 8;
bool saeEnabled = 9;
string groupAttributeName = 10;
string nameIdFormat = 11;
string consumerBinding = 12;
string spEntityId = 13;
}
message IdpList {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema : {
title : "IdpList"
description : "idp list"
read_only : true
}
};
string apiVersion = 1
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "API Version",
description : "API Version of the idp list resource"
default : "usermgmt.k8smgmt.io/v3"
read_only : true
} ];
string kind = 2
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Kind",
description : "Kind of the idp list resource"
default : "IdpList"
read_only : true
} ];
rafay.dev.types.common.v3.ListMetadata metadata = 3
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Metadata",
description : "Metadata of the idp list resource"
read_only : true
} ];
repeated Idp items = 4
[ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
title : "Items",
description : "List of the idp resources"
read_only : true
} ];
}