mirror of https://github.com/portainer/portainer
feat(openamt): Configuration of the OpenAMT capability [INT-6] (#6071)
Co-authored-by: Sven Dowideit <sven.dowideit@portainer.io>pull/6185/head
parent
ab0849d0f3
commit
47c1af93ea
|
@ -44,3 +44,17 @@ func (service *Service) Settings() (*portainer.Settings, error) {
|
||||||
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
func (service *Service) UpdateSettings(settings *portainer.Settings) error {
|
||||||
return internal.UpdateObject(service.connection, BucketName, []byte(settingsKey), settings)
|
return internal.UpdateObject(service.connection, BucketName, []byte(settingsKey), settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *Service) IsFeatureFlagEnabled(feature portainer.Feature) bool {
|
||||||
|
settings, err := service.Settings()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
featureFlagSetting, ok := settings.FeatureFlagSettings[portainer.FeatOpenAMT]
|
||||||
|
if ok {
|
||||||
|
return featureFlagSetting
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -8,17 +8,17 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/portainer/libhelm"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
"github.com/portainer/portainer/api/bolt"
|
||||||
"github.com/portainer/portainer/api/chisel"
|
"github.com/portainer/portainer/api/chisel"
|
||||||
"github.com/portainer/portainer/api/cli"
|
"github.com/portainer/portainer/api/cli"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
"github.com/portainer/portainer/api/docker"
|
"github.com/portainer/portainer/api/docker"
|
||||||
|
|
||||||
"github.com/portainer/libhelm"
|
|
||||||
"github.com/portainer/portainer/api/exec"
|
"github.com/portainer/portainer/api/exec"
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/git"
|
"github.com/portainer/portainer/api/git"
|
||||||
|
"github.com/portainer/portainer/api/hostmanagement/openamt"
|
||||||
"github.com/portainer/portainer/api/http"
|
"github.com/portainer/portainer/api/http"
|
||||||
"github.com/portainer/portainer/api/http/client"
|
"github.com/portainer/portainer/api/http/client"
|
||||||
"github.com/portainer/portainer/api/http/proxy"
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
|
@ -462,12 +462,19 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
log.Fatalf("failed initializing JWT service: %v", err)
|
log.Fatalf("failed initializing JWT service: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = enableFeaturesFromFlags(dataStore, flags)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed enabling feature flag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
ldapService := initLDAPService()
|
ldapService := initLDAPService()
|
||||||
|
|
||||||
oauthService := initOAuthService()
|
oauthService := initOAuthService()
|
||||||
|
|
||||||
gitService := initGitService()
|
gitService := initGitService()
|
||||||
|
|
||||||
|
openAMTService := openamt.NewService(dataStore)
|
||||||
|
|
||||||
cryptoService := initCryptoService()
|
cryptoService := initCryptoService()
|
||||||
|
|
||||||
digitalSignatureService := initDigitalSignatureService()
|
digitalSignatureService := initDigitalSignatureService()
|
||||||
|
@ -537,11 +544,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = enableFeaturesFromFlags(dataStore, flags)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed enabling feature flag: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
err = edge.LoadEdgeJobs(dataStore, reverseTunnelService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed loading edge jobs from database: %v", err)
|
log.Fatalf("failed loading edge jobs from database: %v", err)
|
||||||
|
@ -623,6 +625,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
LDAPService: ldapService,
|
LDAPService: ldapService,
|
||||||
OAuthService: oauthService,
|
OAuthService: oauthService,
|
||||||
GitService: gitService,
|
GitService: gitService,
|
||||||
|
OpenAMTService: openAMTService,
|
||||||
ProxyManager: proxyManager,
|
ProxyManager: proxyManager,
|
||||||
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
|
||||||
KubeConfigService: kubeConfigService,
|
KubeConfigService: kubeConfigService,
|
||||||
|
|
|
@ -46,4 +46,5 @@ require (
|
||||||
k8s.io/api v0.22.2
|
k8s.io/api v0.22.2
|
||||||
k8s.io/apimachinery v0.22.2
|
k8s.io/apimachinery v0.22.2
|
||||||
k8s.io/client-go v0.22.2
|
k8s.io/client-go v0.22.2
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
|
||||||
)
|
)
|
||||||
|
|
|
@ -1124,3 +1124,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZa
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authenticationResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) executeAuthenticationRequest(configuration portainer.OpenAMTConfiguration) (*authenticationResponse, error) {
|
||||||
|
loginURL := fmt.Sprintf("https://%s/mps/login/api/v1/authorize", configuration.MPSURL)
|
||||||
|
|
||||||
|
payload := map[string]string{
|
||||||
|
"username": configuration.Credentials.MPSUser,
|
||||||
|
"password": configuration.Credentials.MPSPassword,
|
||||||
|
}
|
||||||
|
jsonValue, _ := json.Marshal(payload)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, loginURL, bytes.NewBuffer(jsonValue))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
response, err := service.httpsClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseBody, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, readErr
|
||||||
|
}
|
||||||
|
errorResponse := parseError(responseBody)
|
||||||
|
if errorResponse != nil {
|
||||||
|
return nil, errorResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
var token authenticationResponse
|
||||||
|
err = json.Unmarshal(responseBody, &token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token, nil
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CIRAConfig struct {
|
||||||
|
ConfigName string `json:"configName"`
|
||||||
|
MPSServerAddress string `json:"mpsServerAddress"`
|
||||||
|
ServerAddressFormat int `json:"serverAddressFormat"`
|
||||||
|
CommonName string `json:"commonName"`
|
||||||
|
MPSPort int `json:"mpsPort"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
MPSRootCertificate string `json:"mpsRootCertificate"`
|
||||||
|
RegeneratePassword bool `json:"regeneratePassword"`
|
||||||
|
AuthMethod int `json:"authMethod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) createOrUpdateCIRAConfig(configuration portainer.OpenAMTConfiguration, configName string) (*CIRAConfig, error) {
|
||||||
|
ciraConfig, err := service.getCIRAConfig(configuration, configName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
method := http.MethodPost
|
||||||
|
if ciraConfig != nil {
|
||||||
|
method = http.MethodPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
ciraConfig, err = service.saveCIRAConfig(method, configuration, configName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ciraConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) getCIRAConfig(configuration portainer.OpenAMTConfiguration, configName string) (*CIRAConfig, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/ciraconfigs/%s", configuration.MPSURL, configName)
|
||||||
|
|
||||||
|
responseBody, err := service.executeGetRequest(url, configuration.Credentials.MPSToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if responseBody == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result CIRAConfig
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) saveCIRAConfig(method string, configuration portainer.OpenAMTConfiguration, configName string) (*CIRAConfig, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/ciraconfigs", configuration.MPSURL)
|
||||||
|
|
||||||
|
certificate, err := service.getCIRACertificate(configuration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addressFormat, err := addressFormat(configuration.MPSURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := CIRAConfig{
|
||||||
|
ConfigName: configName,
|
||||||
|
MPSServerAddress: configuration.MPSURL,
|
||||||
|
CommonName: configuration.MPSURL,
|
||||||
|
ServerAddressFormat: addressFormat,
|
||||||
|
MPSPort: 4433,
|
||||||
|
Username: "admin",
|
||||||
|
MPSRootCertificate: certificate,
|
||||||
|
RegeneratePassword: false,
|
||||||
|
AuthMethod: 2,
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(config)
|
||||||
|
|
||||||
|
responseBody, err := service.executeSaveRequest(method, url, configuration.Credentials.MPSToken, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result CIRAConfig
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressFormat(url string) (int, error) {
|
||||||
|
ip := net.ParseIP(url)
|
||||||
|
if ip == nil {
|
||||||
|
return 201, nil // FQDN
|
||||||
|
}
|
||||||
|
if strings.Contains(url, ".") {
|
||||||
|
return 3, nil // IPV4
|
||||||
|
}
|
||||||
|
if strings.Contains(url, ":") {
|
||||||
|
return 4, nil // IPV6
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("could not determine server address format for %s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) getCIRACertificate(configuration portainer.OpenAMTConfiguration) (string, error) {
|
||||||
|
loginURL := fmt.Sprintf("https://%s/mps/api/v1/ciracert", configuration.MPSURL)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, loginURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", configuration.Credentials.MPSToken))
|
||||||
|
|
||||||
|
response, err := service.httpsClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return "", errors.New(fmt.Sprintf("unexpected status code %s", response.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(certificate)
|
||||||
|
return base64.StdEncoding.EncodeToString(block.Bytes), nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Domain struct {
|
||||||
|
DomainName string `json:"profileName"`
|
||||||
|
DomainSuffix string `json:"domainSuffix"`
|
||||||
|
ProvisioningCert string `json:"provisioningCert"`
|
||||||
|
ProvisioningCertPassword string `json:"provisioningCertPassword"`
|
||||||
|
ProvisioningCertStorageFormat string `json:"provisioningCertStorageFormat"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (service *Service) createOrUpdateDomain(configuration portainer.OpenAMTConfiguration) (*Domain, error) {
|
||||||
|
domain, err := service.getDomain(configuration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
method := http.MethodPost
|
||||||
|
if domain != nil {
|
||||||
|
method = http.MethodPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
domain, err = service.saveDomain(method, configuration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) getDomain(configuration portainer.OpenAMTConfiguration) (*Domain, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/domains/%s", configuration.MPSURL, configuration.DomainConfiguration.DomainName)
|
||||||
|
|
||||||
|
responseBody, err := service.executeGetRequest(url, configuration.Credentials.MPSToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if responseBody == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Domain
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) saveDomain(method string, configuration portainer.OpenAMTConfiguration) (*Domain, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/domains", configuration.MPSURL)
|
||||||
|
|
||||||
|
profile := Domain{
|
||||||
|
DomainName: configuration.DomainConfiguration.DomainName,
|
||||||
|
DomainSuffix: configuration.DomainConfiguration.DomainName,
|
||||||
|
ProvisioningCert: configuration.DomainConfiguration.CertFileText,
|
||||||
|
ProvisioningCertPassword: configuration.DomainConfiguration.CertPassword,
|
||||||
|
ProvisioningCertStorageFormat: "string",
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(profile)
|
||||||
|
|
||||||
|
responseBody, err := service.executeSaveRequest(method, url, configuration.Credentials.MPSToken, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Domain
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Profile struct {
|
||||||
|
ProfileName string `json:"profileName"`
|
||||||
|
Activation string `json:"activation"`
|
||||||
|
CIRAConfigName *string `json:"ciraConfigName"`
|
||||||
|
GenerateRandomAMTPassword bool `json:"generateRandomPassword"`
|
||||||
|
AMTPassword string `json:"amtPassword"`
|
||||||
|
GenerateRandomMEBxPassword bool `json:"generateRandomMEBxPassword"`
|
||||||
|
MEBXPassword string `json:"mebxPassword"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
DHCPEnabled bool `json:"dhcpEnabled"`
|
||||||
|
TenantId string `json:"tenantId"`
|
||||||
|
WIFIConfigs []ProfileWifiConfig `json:"wifiConfigs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileWifiConfig struct {
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
ProfileName string `json:"profileName"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (service *Service) createOrUpdateAMTProfile(configuration portainer.OpenAMTConfiguration, profileName string, ciraConfigName string, wirelessConfig string) (*Profile, error) {
|
||||||
|
profile, err := service.getAMTProfile(configuration, profileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
method := http.MethodPost
|
||||||
|
if profile != nil {
|
||||||
|
method = http.MethodPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err = service.saveAMTProfile(method, configuration, profileName, ciraConfigName, wirelessConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) getAMTProfile(configuration portainer.OpenAMTConfiguration, profileName string) (*Profile, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/profiles/%s", configuration.MPSURL, profileName)
|
||||||
|
|
||||||
|
responseBody, err := service.executeGetRequest(url, configuration.Credentials.MPSToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if responseBody == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Profile
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) saveAMTProfile(method string, configuration portainer.OpenAMTConfiguration, profileName string, ciraConfigName string, wirelessConfig string) (*Profile, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/profiles", configuration.MPSURL)
|
||||||
|
|
||||||
|
profile := Profile{
|
||||||
|
ProfileName: profileName,
|
||||||
|
Activation: "acmactivate",
|
||||||
|
GenerateRandomAMTPassword: false,
|
||||||
|
GenerateRandomMEBxPassword: false,
|
||||||
|
AMTPassword: configuration.Credentials.MPSPassword,
|
||||||
|
MEBXPassword: configuration.Credentials.MPSPassword,
|
||||||
|
CIRAConfigName: &ciraConfigName,
|
||||||
|
Tags: []string{},
|
||||||
|
DHCPEnabled: true,
|
||||||
|
}
|
||||||
|
if wirelessConfig != "" {
|
||||||
|
profile.WIFIConfigs = []ProfileWifiConfig{
|
||||||
|
{
|
||||||
|
Priority: 1,
|
||||||
|
ProfileName: DefaultWirelessConfigName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(profile)
|
||||||
|
|
||||||
|
responseBody, err := service.executeSaveRequest(method, url, configuration.Credentials.MPSToken, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Profile
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
WirelessProfile struct {
|
||||||
|
ProfileName string `json:"profileName"`
|
||||||
|
AuthenticationMethod int `json:"authenticationMethod"`
|
||||||
|
EncryptionMethod int `json:"encryptionMethod"`
|
||||||
|
SSID string `json:"ssid"`
|
||||||
|
PSKPassphrase string `json:"pskPassphrase"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (service *Service) createOrUpdateWirelessConfig(configuration portainer.OpenAMTConfiguration, wirelessConfigName string) (*WirelessProfile, error) {
|
||||||
|
wirelessConfig, err := service.getWirelessConfig(configuration, wirelessConfigName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
method := http.MethodPost
|
||||||
|
if wirelessConfig != nil {
|
||||||
|
method = http.MethodPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
wirelessConfig, err = service.saveWirelessConfig(method, configuration, wirelessConfigName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return wirelessConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) getWirelessConfig(configuration portainer.OpenAMTConfiguration, configName string) (*WirelessProfile, error) {
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/wirelessconfigs/%s", configuration.MPSURL, configName)
|
||||||
|
|
||||||
|
responseBody, err := service.executeGetRequest(url, configuration.Credentials.MPSToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if responseBody == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result WirelessProfile
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) saveWirelessConfig(method string, configuration portainer.OpenAMTConfiguration, configName string) (*WirelessProfile, error) {
|
||||||
|
parsedAuthenticationMethod, err := strconv.Atoi(configuration.WirelessConfiguration.AuthenticationMethod)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing wireless authentication method: %s", err.Error())
|
||||||
|
}
|
||||||
|
parsedEncryptionMethod, err := strconv.Atoi(configuration.WirelessConfiguration.EncryptionMethod)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing wireless encryption method: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://%s/rps/api/v1/admin/wirelessconfigs", configuration.MPSURL)
|
||||||
|
|
||||||
|
config := WirelessProfile{
|
||||||
|
ProfileName: configName,
|
||||||
|
AuthenticationMethod: parsedAuthenticationMethod,
|
||||||
|
EncryptionMethod: parsedEncryptionMethod,
|
||||||
|
SSID: configuration.WirelessConfiguration.SSID,
|
||||||
|
PSKPassphrase: configuration.WirelessConfiguration.PskPass,
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(config)
|
||||||
|
|
||||||
|
responseBody, err := service.executeSaveRequest(method, url, configuration.Credentials.MPSToken, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result WirelessProfile
|
||||||
|
err = json.Unmarshal(responseBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultCIRAConfigName = "ciraConfigDefault"
|
||||||
|
DefaultWirelessConfigName = "wirelessProfileDefault"
|
||||||
|
DefaultProfileName = "profileAMTDefault"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service for managing an OpenAMT server.
|
||||||
|
type Service struct {
|
||||||
|
httpsClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService initializes a new service.
|
||||||
|
func NewService(dataStore portainer.DataStore) *Service {
|
||||||
|
if !dataStore.Settings().IsFeatureFlagEnabled(portainer.FeatOpenAMT) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Service{
|
||||||
|
httpsClient: &http.Client{
|
||||||
|
Timeout: time.Second * time.Duration(5),
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type openAMTError struct {
|
||||||
|
ErrorMsg string `json:"message"`
|
||||||
|
Errors []struct {
|
||||||
|
ErrorMsg string `json:"msg"`
|
||||||
|
} `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseError(responseBody []byte) error {
|
||||||
|
var errorResponse openAMTError
|
||||||
|
err := json.Unmarshal(responseBody, &errorResponse)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorResponse.Errors) > 0 {
|
||||||
|
return errors.New(errorResponse.Errors[0].ErrorMsg)
|
||||||
|
}
|
||||||
|
if errorResponse.ErrorMsg != "" {
|
||||||
|
return errors.New(errorResponse.ErrorMsg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) ConfigureDefault(configuration portainer.OpenAMTConfiguration) error {
|
||||||
|
token, err := service.executeAuthenticationRequest(configuration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configuration.Credentials.MPSToken = token.Token
|
||||||
|
|
||||||
|
ciraConfig, err := service.createOrUpdateCIRAConfig(configuration, DefaultCIRAConfigName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wirelessConfigName := ""
|
||||||
|
if configuration.WirelessConfiguration != nil {
|
||||||
|
wirelessConfig, err := service.createOrUpdateWirelessConfig(configuration, DefaultWirelessConfigName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wirelessConfigName = wirelessConfig.ProfileName
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = service.createOrUpdateAMTProfile(configuration, DefaultProfileName, ciraConfig.ConfigName, wirelessConfigName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = service.createOrUpdateDomain(configuration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) executeSaveRequest(method string, url string, token string, payload []byte) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
|
||||||
|
response, err := service.httpsClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseBody, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode < 200 || response.StatusCode > 300 {
|
||||||
|
errorResponse := parseError(responseBody)
|
||||||
|
if errorResponse != nil {
|
||||||
|
return nil, errorResponse
|
||||||
|
}
|
||||||
|
return nil, errors.New(fmt.Sprintf("unexpected status code %s", response.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *Service) executeGetRequest(url string, token string) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
|
||||||
|
response, err := service.httpsClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseBody, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode < 200 || response.StatusCode > 300 {
|
||||||
|
if response.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
errorResponse := parseError(responseBody)
|
||||||
|
if errorResponse != nil {
|
||||||
|
return nil, errorResponse
|
||||||
|
}
|
||||||
|
return nil, errors.New(fmt.Sprintf("unexpected status code %s", response.Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody, nil
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/handler/endpoints"
|
"github.com/portainer/portainer/api/http/handler/endpoints"
|
||||||
"github.com/portainer/portainer/api/http/handler/file"
|
"github.com/portainer/portainer/api/http/handler/file"
|
||||||
"github.com/portainer/portainer/api/http/handler/helm"
|
"github.com/portainer/portainer/api/http/handler/helm"
|
||||||
|
"github.com/portainer/portainer/api/http/handler/hostmanagement/openamt"
|
||||||
"github.com/portainer/portainer/api/http/handler/kubernetes"
|
"github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||||
"github.com/portainer/portainer/api/http/handler/motd"
|
"github.com/portainer/portainer/api/http/handler/motd"
|
||||||
|
@ -62,6 +63,7 @@ type Handler struct {
|
||||||
RoleHandler *roles.Handler
|
RoleHandler *roles.Handler
|
||||||
SettingsHandler *settings.Handler
|
SettingsHandler *settings.Handler
|
||||||
SSLHandler *ssl.Handler
|
SSLHandler *ssl.Handler
|
||||||
|
OpenAMTHandler *openamt.Handler
|
||||||
StackHandler *stacks.Handler
|
StackHandler *stacks.Handler
|
||||||
StatusHandler *status.Handler
|
StatusHandler *status.Handler
|
||||||
StorybookHandler *storybook.Handler
|
StorybookHandler *storybook.Handler
|
||||||
|
@ -221,6 +223,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.StripPrefix("/api", h.UserHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api", h.UserHandler).ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/ssl"):
|
case strings.HasPrefix(r.URL.Path, "/api/ssl"):
|
||||||
http.StripPrefix("/api", h.SSLHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api", h.SSLHandler).ServeHTTP(w, r)
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/open_amt"):
|
||||||
|
if h.OpenAMTHandler != nil {
|
||||||
|
http.StripPrefix("/api", h.OpenAMTHandler).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/teams"):
|
case strings.HasPrefix(r.URL.Path, "/api/teams"):
|
||||||
http.StripPrefix("/api", h.TeamHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api", h.TeamHandler).ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/team_memberships"):
|
case strings.HasPrefix(r.URL.Path, "/api/team_memberships"):
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler is the HTTP handler used to handle OpenAMT operations.
|
||||||
|
type Handler struct {
|
||||||
|
*mux.Router
|
||||||
|
OpenAMTService portainer.OpenAMTService
|
||||||
|
DataStore portainer.DataStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler returns a new Handler
|
||||||
|
func NewHandler(bouncer *security.RequestBouncer, dataStore portainer.DataStore) (*Handler, error) {
|
||||||
|
if !dataStore.Settings().IsFeatureFlagEnabled(portainer.FeatOpenAMT) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &Handler{
|
||||||
|
Router: mux.NewRouter(),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Handle("/open_amt", bouncer.AdminAccess(httperror.LoggerHandler(h.openAMTConfigureDefault))).Methods(http.MethodPost)
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
package openamt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
|
||||||
|
httperror "github.com/portainer/libhttp/error"
|
||||||
|
"github.com/portainer/libhttp/request"
|
||||||
|
"github.com/portainer/libhttp/response"
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type openAMTConfigureDefaultPayload struct {
|
||||||
|
EnableOpenAMT bool
|
||||||
|
MPSURL string
|
||||||
|
MPSUser string
|
||||||
|
MPSPassword string
|
||||||
|
CertFileText string
|
||||||
|
CertPassword string
|
||||||
|
DomainName string
|
||||||
|
UseWirelessConfig bool
|
||||||
|
WifiAuthenticationMethod string
|
||||||
|
WifiEncryptionMethod string
|
||||||
|
WifiSSID string
|
||||||
|
WifiPskPass string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (payload *openAMTConfigureDefaultPayload) Validate(r *http.Request) error {
|
||||||
|
if payload.EnableOpenAMT {
|
||||||
|
if payload.MPSURL == "" {
|
||||||
|
return errors.New("MPS Url must be provided")
|
||||||
|
}
|
||||||
|
if payload.MPSUser == "" {
|
||||||
|
return errors.New("MPS User must be provided")
|
||||||
|
}
|
||||||
|
if payload.MPSPassword == "" {
|
||||||
|
return errors.New("MPS Password must be provided")
|
||||||
|
}
|
||||||
|
if payload.DomainName == "" {
|
||||||
|
return errors.New("domain name must be provided")
|
||||||
|
}
|
||||||
|
if payload.CertFileText == "" {
|
||||||
|
return errors.New("certificate file must be provided")
|
||||||
|
}
|
||||||
|
if payload.CertPassword == "" {
|
||||||
|
return errors.New("certificate password must be provided")
|
||||||
|
}
|
||||||
|
if payload.UseWirelessConfig {
|
||||||
|
if payload.WifiAuthenticationMethod == "" {
|
||||||
|
return errors.New("wireless authentication method must be provided")
|
||||||
|
}
|
||||||
|
if payload.WifiEncryptionMethod == "" {
|
||||||
|
return errors.New("wireless encryption method must be provided")
|
||||||
|
}
|
||||||
|
if payload.WifiSSID == "" {
|
||||||
|
return errors.New("wireless config SSID must be provided")
|
||||||
|
}
|
||||||
|
if payload.WifiPskPass == "" {
|
||||||
|
return errors.New("wireless config PSK passphrase must be provided")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @id OpenAMTConfigureDefault
|
||||||
|
// @summary Enable Portainer's OpenAMT capabilities
|
||||||
|
// @description Enable Portainer's OpenAMT capabilities
|
||||||
|
// @description **Access policy**: administrator
|
||||||
|
// @tags intel
|
||||||
|
// @security jwt
|
||||||
|
// @accept json
|
||||||
|
// @produce json
|
||||||
|
// @param body body openAMTConfigureDefaultPayload true "OpenAMT Settings"
|
||||||
|
// @success 204 "Success"
|
||||||
|
// @failure 400 "Invalid request"
|
||||||
|
// @failure 403 "Permission denied to access settings"
|
||||||
|
// @failure 500 "Server error"
|
||||||
|
// @router /open_amt [post]
|
||||||
|
func (handler *Handler) openAMTConfigureDefault(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
var payload openAMTConfigureDefaultPayload
|
||||||
|
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Invalid request payload")
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.EnableOpenAMT {
|
||||||
|
certificateErr := validateCertificate(payload.CertFileText, payload.CertPassword)
|
||||||
|
if certificateErr != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Error validating certificate", Err: certificateErr}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.enableOpenAMT(payload)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Error enabling OpenAMT", Err: err}
|
||||||
|
}
|
||||||
|
return response.Empty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.disableOpenAMT()
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Error disabling OpenAMT", Err: err}
|
||||||
|
}
|
||||||
|
return response.Empty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCertificate(certificateRaw string, certificatePassword string) error {
|
||||||
|
certificateData, err := base64.StdEncoding.Strict().DecodeString(certificateRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, certificate, _, err := pkcs12.DecodeChain(certificateData, certificatePassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate == nil {
|
||||||
|
return errors.New("certificate could not be decoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer := certificate.Issuer.CommonName
|
||||||
|
if !isValidIssuer(issuer) {
|
||||||
|
return fmt.Errorf("certificate issuer is invalid: %v", issuer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidIssuer(issuer string) bool {
|
||||||
|
formattedIssuer := strings.ToLower(strings.ReplaceAll(issuer, " ", ""))
|
||||||
|
return strings.Contains(formattedIssuer, "comodo") ||
|
||||||
|
strings.Contains(formattedIssuer, "digicert") ||
|
||||||
|
strings.Contains(formattedIssuer, "entrust") ||
|
||||||
|
strings.Contains(formattedIssuer, "godaddy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) enableOpenAMT(configurationPayload openAMTConfigureDefaultPayload) error {
|
||||||
|
configuration := portainer.OpenAMTConfiguration{
|
||||||
|
Enabled: true,
|
||||||
|
MPSURL: configurationPayload.MPSURL,
|
||||||
|
Credentials: portainer.MPSCredentials{
|
||||||
|
MPSUser: configurationPayload.MPSUser,
|
||||||
|
MPSPassword: configurationPayload.MPSPassword,
|
||||||
|
},
|
||||||
|
DomainConfiguration: portainer.DomainConfiguration{
|
||||||
|
CertFileText: configurationPayload.CertFileText,
|
||||||
|
CertPassword: configurationPayload.CertPassword,
|
||||||
|
DomainName: configurationPayload.DomainName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if configurationPayload.UseWirelessConfig {
|
||||||
|
configuration.WirelessConfiguration = &portainer.WirelessConfiguration{
|
||||||
|
AuthenticationMethod: configurationPayload.WifiAuthenticationMethod,
|
||||||
|
EncryptionMethod: configurationPayload.WifiEncryptionMethod,
|
||||||
|
SSID: configurationPayload.WifiSSID,
|
||||||
|
PskPass: configurationPayload.WifiPskPass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handler.OpenAMTService.ConfigureDefault(configuration)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("error configuring OpenAMT server")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.saveConfiguration(configuration)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("error updating OpenAMT configurations")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Info("OpenAMT successfully enabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) saveConfiguration(configuration portainer.OpenAMTConfiguration) error {
|
||||||
|
settings, err := handler.DataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.Credentials.MPSToken = ""
|
||||||
|
|
||||||
|
settings.OpenAMTConfiguration = configuration
|
||||||
|
err = handler.DataStore.Settings().UpdateSettings(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) disableOpenAMT() error {
|
||||||
|
settings, err := handler.DataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.OpenAMTConfiguration.Enabled = false
|
||||||
|
|
||||||
|
err = handler.DataStore.Settings().UpdateSettings(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Info("OpenAMT successfully disabled")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/http/handler/endpoints"
|
"github.com/portainer/portainer/api/http/handler/endpoints"
|
||||||
"github.com/portainer/portainer/api/http/handler/file"
|
"github.com/portainer/portainer/api/http/handler/file"
|
||||||
"github.com/portainer/portainer/api/http/handler/helm"
|
"github.com/portainer/portainer/api/http/handler/helm"
|
||||||
|
"github.com/portainer/portainer/api/http/handler/hostmanagement/openamt"
|
||||||
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
|
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||||
"github.com/portainer/portainer/api/http/handler/motd"
|
"github.com/portainer/portainer/api/http/handler/motd"
|
||||||
|
@ -75,6 +76,7 @@ type Server struct {
|
||||||
FileService portainer.FileService
|
FileService portainer.FileService
|
||||||
DataStore portainer.DataStore
|
DataStore portainer.DataStore
|
||||||
GitService portainer.GitService
|
GitService portainer.GitService
|
||||||
|
OpenAMTService portainer.OpenAMTService
|
||||||
JWTService portainer.JWTService
|
JWTService portainer.JWTService
|
||||||
LDAPService portainer.LDAPService
|
LDAPService portainer.LDAPService
|
||||||
OAuthService portainer.OAuthService
|
OAuthService portainer.OAuthService
|
||||||
|
@ -203,6 +205,15 @@ func (server *Server) Start() error {
|
||||||
var sslHandler = sslhandler.NewHandler(requestBouncer)
|
var sslHandler = sslhandler.NewHandler(requestBouncer)
|
||||||
sslHandler.SSLService = server.SSLService
|
sslHandler.SSLService = server.SSLService
|
||||||
|
|
||||||
|
openAMTHandler, err := openamt.NewHandler(requestBouncer, server.DataStore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if openAMTHandler != nil {
|
||||||
|
openAMTHandler.OpenAMTService = server.OpenAMTService
|
||||||
|
openAMTHandler.DataStore = server.DataStore
|
||||||
|
}
|
||||||
|
|
||||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||||
stackHandler.DataStore = server.DataStore
|
stackHandler.DataStore = server.DataStore
|
||||||
stackHandler.DockerClientFactory = server.DockerClientFactory
|
stackHandler.DockerClientFactory = server.DockerClientFactory
|
||||||
|
@ -268,6 +279,7 @@ func (server *Server) Start() error {
|
||||||
HelmTemplatesHandler: helmTemplatesHandler,
|
HelmTemplatesHandler: helmTemplatesHandler,
|
||||||
KubernetesHandler: kubernetesHandler,
|
KubernetesHandler: kubernetesHandler,
|
||||||
MOTDHandler: motdHandler,
|
MOTDHandler: motdHandler,
|
||||||
|
OpenAMTHandler: openAMTHandler,
|
||||||
RegistryHandler: registryHandler,
|
RegistryHandler: registryHandler,
|
||||||
ResourceControlHandler: resourceControlHandler,
|
ResourceControlHandler: resourceControlHandler,
|
||||||
SettingsHandler: settingsHandler,
|
SettingsHandler: settingsHandler,
|
||||||
|
|
|
@ -86,6 +86,9 @@ func (s *stubSettingsService) UpdateSettings(settings *portainer.Settings) error
|
||||||
s.settings = settings
|
s.settings = settings
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (s *stubSettingsService) IsFeatureFlagEnabled(feature portainer.Feature) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
func WithSettingsService(settings *portainer.Settings) datastoreOption {
|
func WithSettingsService(settings *portainer.Settings) datastoreOption {
|
||||||
return func(d *datastore) {
|
return func(d *datastore) {
|
||||||
d.settings = &stubSettingsService{
|
d.settings = &stubSettingsService{
|
||||||
|
|
|
@ -39,6 +39,34 @@ type (
|
||||||
AuthenticationKey string `json:"AuthenticationKey" example:"cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="`
|
AuthenticationKey string `json:"AuthenticationKey" example:"cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAMTConfiguration represents the credentials and configurations used to connect to an OpenAMT MPS server
|
||||||
|
OpenAMTConfiguration struct {
|
||||||
|
Enabled bool `json:"Enabled"`
|
||||||
|
MPSURL string `json:"MPSURL"`
|
||||||
|
Credentials MPSCredentials `json:"Credentials"`
|
||||||
|
DomainConfiguration DomainConfiguration `json:"DomainConfiguration"`
|
||||||
|
WirelessConfiguration *WirelessConfiguration `json:"WirelessConfiguration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
MPSCredentials struct {
|
||||||
|
MPSUser string `json:"MPSUser"`
|
||||||
|
MPSPassword string `json:"MPSPassword"`
|
||||||
|
MPSToken string `json:"MPSToken"` // retrieved from API
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainConfiguration struct {
|
||||||
|
CertFileText string `json:"CertFileText"`
|
||||||
|
CertPassword string `json:"CertPassword"`
|
||||||
|
DomainName string `json:"DomainName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
WirelessConfiguration struct {
|
||||||
|
AuthenticationMethod string `json:"AuthenticationMethod"`
|
||||||
|
EncryptionMethod string `json:"EncryptionMethod"`
|
||||||
|
SSID string `json:"SSID"`
|
||||||
|
PskPass string `json:"PskPass"`
|
||||||
|
}
|
||||||
|
|
||||||
// CLIFlags represents the available flags on the CLI
|
// CLIFlags represents the available flags on the CLI
|
||||||
CLIFlags struct {
|
CLIFlags struct {
|
||||||
Addr *string
|
Addr *string
|
||||||
|
@ -707,6 +735,7 @@ type (
|
||||||
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod" example:"1"`
|
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod" example:"1"`
|
||||||
LDAPSettings LDAPSettings `json:"LDAPSettings" example:""`
|
LDAPSettings LDAPSettings `json:"LDAPSettings" example:""`
|
||||||
OAuthSettings OAuthSettings `json:"OAuthSettings" example:""`
|
OAuthSettings OAuthSettings `json:"OAuthSettings" example:""`
|
||||||
|
OpenAMTConfiguration OpenAMTConfiguration `json:"OpenAMTConfiguration" example:""`
|
||||||
FeatureFlagSettings map[Feature]bool `json:"FeatureFlagSettings" example:""`
|
FeatureFlagSettings map[Feature]bool `json:"FeatureFlagSettings" example:""`
|
||||||
// The interval in which environment(endpoint) snapshots are created
|
// The interval in which environment(endpoint) snapshots are created
|
||||||
SnapshotInterval string `json:"SnapshotInterval" example:"5m"`
|
SnapshotInterval string `json:"SnapshotInterval" example:"5m"`
|
||||||
|
@ -1256,6 +1285,11 @@ type (
|
||||||
LatestCommitID(repositoryURL, referenceName, username, password string) (string, error)
|
LatestCommitID(repositoryURL, referenceName, username, password string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAMTService represents a service for managing OpenAMT
|
||||||
|
OpenAMTService interface {
|
||||||
|
ConfigureDefault(configuration OpenAMTConfiguration) error
|
||||||
|
}
|
||||||
|
|
||||||
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
|
// HelmUserRepositoryService represents a service to manage HelmUserRepositories
|
||||||
HelmUserRepositoryService interface {
|
HelmUserRepositoryService interface {
|
||||||
HelmUserRepositoryByUserID(userID UserID) ([]HelmUserRepository, error)
|
HelmUserRepositoryByUserID(userID UserID) ([]HelmUserRepository, error)
|
||||||
|
@ -1360,6 +1394,7 @@ type (
|
||||||
SettingsService interface {
|
SettingsService interface {
|
||||||
Settings() (*Settings, error)
|
Settings() (*Settings, error)
|
||||||
UpdateSettings(settings *Settings) error
|
UpdateSettings(settings *Settings) error
|
||||||
|
IsFeatureFlagEnabled(feature Feature) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server defines the interface to serve the API
|
// Server defines the interface to serve the API
|
||||||
|
|
|
@ -4,6 +4,7 @@ export function SettingsViewModel(data) {
|
||||||
this.AuthenticationMethod = data.AuthenticationMethod;
|
this.AuthenticationMethod = data.AuthenticationMethod;
|
||||||
this.LDAPSettings = data.LDAPSettings;
|
this.LDAPSettings = data.LDAPSettings;
|
||||||
this.OAuthSettings = new OAuthSettingsViewModel(data.OAuthSettings);
|
this.OAuthSettings = new OAuthSettingsViewModel(data.OAuthSettings);
|
||||||
|
this.OpenAMTConfiguration = data.OpenAMTConfiguration;
|
||||||
this.SnapshotInterval = data.SnapshotInterval;
|
this.SnapshotInterval = data.SnapshotInterval;
|
||||||
this.TemplatesURL = data.TemplatesURL;
|
this.TemplatesURL = data.TemplatesURL;
|
||||||
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
const API_ENDPOINT_OPEN_AMT = 'api/open_amt';
|
||||||
|
|
||||||
|
angular.module('portainer.app').factory('OpenAMT', OpenAMTFactory);
|
||||||
|
|
||||||
|
/* @ngInject */
|
||||||
|
function OpenAMTFactory($resource) {
|
||||||
|
return $resource(
|
||||||
|
API_ENDPOINT_OPEN_AMT,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
submit: { method: 'POST' },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
angular.module('portainer.app').service('OpenAMTService', OpenAMTServiceFactory);
|
||||||
|
|
||||||
|
/* @ngInject */
|
||||||
|
function OpenAMTServiceFactory(OpenAMT) {
|
||||||
|
return {
|
||||||
|
submit,
|
||||||
|
};
|
||||||
|
|
||||||
|
function submit(formValues) {
|
||||||
|
return OpenAMT.submit(formValues).$promise;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { sslCertificate } from './ssl-certificate';
|
import { sslCertificate } from './ssl-certificate';
|
||||||
|
import { openAMT } from './open-amt';
|
||||||
|
|
||||||
export default angular.module('portainer.settings.general', []).component('sslCertificateSettings', sslCertificate).name;
|
export default angular.module('portainer.settings.general', []).component('sslCertificateSettings', sslCertificate).component('openAmtSettings', openAMT).name;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import controller from './open-amt.controller.js';
|
||||||
|
|
||||||
|
export const openAMT = {
|
||||||
|
templateUrl: './open-amt.html',
|
||||||
|
controller,
|
||||||
|
};
|
|
@ -0,0 +1,111 @@
|
||||||
|
class OpenAmtController {
|
||||||
|
/* @ngInject */
|
||||||
|
constructor($async, $state, OpenAMTService, SettingsService, Notifications) {
|
||||||
|
Object.assign(this, { $async, $state, OpenAMTService, SettingsService, Notifications });
|
||||||
|
|
||||||
|
this.originalValues = {};
|
||||||
|
this.formValues = {
|
||||||
|
enableOpenAMT: false,
|
||||||
|
mpsURL: '',
|
||||||
|
mpsUser: '',
|
||||||
|
mpsPassword: '',
|
||||||
|
domainName: '',
|
||||||
|
certFile: null,
|
||||||
|
certPassword: '',
|
||||||
|
useWirelessConfig: false,
|
||||||
|
wifiAuthenticationMethod: '4',
|
||||||
|
wifiEncryptionMethod: '3',
|
||||||
|
wifiSsid: '',
|
||||||
|
wifiPskPass: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.originalValues = {
|
||||||
|
...this.formValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
actionInProgress: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.save = this.save.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFormChanged() {
|
||||||
|
return Object.entries(this.originalValues).some(([key, value]) => value !== this.formValues[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFormValid() {
|
||||||
|
return !this.formValues.enableOpenAMT || this.formValues.certFile != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const file = this.formValues.certFile;
|
||||||
|
if (file) {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.fileName = file.name;
|
||||||
|
fileReader.onload = (e) => {
|
||||||
|
const base64 = e.target.result;
|
||||||
|
// remove prefix of "data:application/x-pkcs12;base64," returned by "readAsDataURL()"
|
||||||
|
const index = base64.indexOf('base64,');
|
||||||
|
const cert = base64.substring(index + 7, base64.length);
|
||||||
|
resolve(cert);
|
||||||
|
};
|
||||||
|
fileReader.onerror = () => {
|
||||||
|
reject(new Error('error reading provisioning certificate file'));
|
||||||
|
};
|
||||||
|
fileReader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
return this.$async(async () => {
|
||||||
|
this.state.actionInProgress = true;
|
||||||
|
try {
|
||||||
|
this.formValues.certFileText = this.formValues.certFile ? await this.readFile(this.formValues.certFile) : null;
|
||||||
|
await this.OpenAMTService.submit(this.formValues);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
this.Notifications.success(`OpenAMT successfully ${this.formValues.enableOpenAMT ? 'enabled' : 'disabled'}`);
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Failed applying changes');
|
||||||
|
}
|
||||||
|
this.state.actionInProgress = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async $onInit() {
|
||||||
|
return this.$async(async () => {
|
||||||
|
try {
|
||||||
|
const data = await this.SettingsService.settings();
|
||||||
|
const config = data.OpenAMTConfiguration;
|
||||||
|
|
||||||
|
if (config) {
|
||||||
|
this.formValues = {
|
||||||
|
...this.formValues,
|
||||||
|
enableOpenAMT: config.Enabled,
|
||||||
|
mpsURL: config.MPSURL,
|
||||||
|
mpsUser: config.Credentials.MPSUser,
|
||||||
|
domainName: config.DomainConfiguration.DomainName,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.WirelessConfiguration) {
|
||||||
|
this.formValues.useWirelessConfig = true;
|
||||||
|
this.formValues.wifiAuthenticationMethod = config.WirelessConfiguration.AuthenticationMethod;
|
||||||
|
this.formValues.wifiEncryptionMethod = config.WirelessConfiguration.EncryptionMethod;
|
||||||
|
this.formValues.wifiSsid = config.WirelessConfiguration.SSID;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.originalValues = {
|
||||||
|
...this.formValues,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Failed loading settings');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OpenAmtController;
|
|
@ -0,0 +1,259 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-laptop" title-text="Intel OpenAMT"></rd-widget-header>
|
||||||
|
<rd-widget-body>
|
||||||
|
<form class="form-horizontal" name="openAMTForm">
|
||||||
|
<por-switch-field ng-model="$ctrl.formValues.enableOpenAMT" label="Enable edge OpenAMT"></por-switch-field>
|
||||||
|
<span class="small">
|
||||||
|
<p class="text-muted" style="margin-top: 10px;">
|
||||||
|
<i class="fa fa-exclamation-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
When enabled, this will allow Portainer to interact with an OpenAMT MPS API.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div ng-show="$ctrl.formValues.enableOpenAMT">
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="mps_url" class="col-sm-3 control-label text-left">
|
||||||
|
MPS URL
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.mpsURL"
|
||||||
|
id="mps_url"
|
||||||
|
name="mps_url"
|
||||||
|
placeholder="Enter the MPS URL"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.mps_url.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.mps_url.$error">
|
||||||
|
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="mps_user" class="col-sm-3 control-label text-left">
|
||||||
|
MPS User
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.mpsUser"
|
||||||
|
id="mps_user"
|
||||||
|
name="mps_user"
|
||||||
|
placeholder="Enter the MPS User"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.mps_user.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.mps_user.$error">
|
||||||
|
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="mps_user" class="col-sm-3 control-label text-left">
|
||||||
|
MPS Password
|
||||||
|
<portainer-tooltip
|
||||||
|
position="bottom"
|
||||||
|
message="Needs to be 8-32 characters including one uppercase, one lowercase letters, one base-10 digit and one special character."
|
||||||
|
></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.mpsPassword"
|
||||||
|
id="mps_password"
|
||||||
|
name="mps_password"
|
||||||
|
placeholder="Enter the MPS Password"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.mps_password.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.mps_password.$error">
|
||||||
|
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domain_name" class="col-sm-3 control-label text-left">
|
||||||
|
Domain Name
|
||||||
|
<portainer-tooltip position="bottom" message="Enter the FQDN that is associated with the provisioning certificate (i.e amtdomain.com)"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.domainName"
|
||||||
|
id="domain_name"
|
||||||
|
name="domain_name"
|
||||||
|
placeholder="Enter the Domain Name"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.domain_name.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.domain_name.$error">
|
||||||
|
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="certificate_file" class="col-sm-3 control-label text-left">
|
||||||
|
Provisioning Certificate File (.pfx)
|
||||||
|
<portainer-tooltip
|
||||||
|
position="bottom"
|
||||||
|
message="Supported CAs are Comodo, DigiCert, Entrust and GoDaddy. The certificate must contain the private key."
|
||||||
|
></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<button style="margin-left: 0px !important;" class="btn btn-sm btn-primary" ngf-select ng-model="$ctrl.formValues.certFile" ngf-pattern=".pfx" name="certFile">
|
||||||
|
Upload file
|
||||||
|
</button>
|
||||||
|
<span style="margin-left: 5px;">
|
||||||
|
{{ $ctrl.formValues.certFile.name }}
|
||||||
|
<i class="fa fa-times red-icon" ng-if="!$ctrl.formValues.certFile" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.certFile.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.certFile.$error">
|
||||||
|
<p ng-message="pattern"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> File type is invalid.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="certificate_password" class="col-sm-3 control-label text-left">
|
||||||
|
Provisioning Certificate Password
|
||||||
|
<portainer-tooltip
|
||||||
|
position="bottom"
|
||||||
|
message="Needs to be 8-32 characters including one uppercase, one lowercase letters, one base-10 digit and one special character."
|
||||||
|
></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.certPassword"
|
||||||
|
id="certificate_password"
|
||||||
|
name="certificate_password"
|
||||||
|
placeholder="**********"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="openAMTForm.certificate_password.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="openAMTForm.certificate_password.$error">
|
||||||
|
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<por-switch-field ng-model="$ctrl.formValues.useWirelessConfig" label="Wireless Configuration"></por-switch-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="$ctrl.formValues.useWirelessConfig">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi_auth_method" class="col-sm-3 control-label text-left">
|
||||||
|
Authentication Method
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select class="form-control" ng-model="$ctrl.formValues.wifiAuthenticationMethod">
|
||||||
|
<option value="4">WPA PSK</option>
|
||||||
|
<option value="6">WPA2 PSK</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi_encrypt_method" class="col-sm-3 control-label text-left">
|
||||||
|
Encryption Method
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select class="form-control" ng-model="$ctrl.formValues.wifiEncryptionMethod" id="wifi_encrypt_method">
|
||||||
|
<option value="3">TKIP</option>
|
||||||
|
<option value="4">CCMP</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi_ssid" class="col-sm-3 control-label text-left">
|
||||||
|
SSID
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.wifiSsid"
|
||||||
|
id="wifi_ssid"
|
||||||
|
placeholder="SSIID"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT && $ctrl.formValues.useWirelessConfig"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi_pass" class="col-sm-3 control-label text-left">
|
||||||
|
PSK Passphrase
|
||||||
|
<portainer-tooltip position="bottom" message="PSK Passphrase length should be greater than or equal to 8 and less than or equal to 63"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.wifiPskPass"
|
||||||
|
id="wifi_pass"
|
||||||
|
placeholder="******"
|
||||||
|
ng-required="$ctrl.formValues.enableOpenAMT && $ctrl.formValues.useWirelessConfig"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
ng-disabled="$ctrl.state.actionInProgress || !openAMTForm.$valid || !$ctrl.isFormChanged() || !$ctrl.isFormValid()"
|
||||||
|
ng-click="$ctrl.save()"
|
||||||
|
button-spinner="$ctrl.state.actionInProgress"
|
||||||
|
>
|
||||||
|
<span ng-hide="$ctrl.state.actionInProgress">Save Settings</span>
|
||||||
|
<span ng-show="$ctrl.state.actionInProgress">In progress...</span>
|
||||||
|
</button>
|
||||||
|
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</rd-widget-body>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -184,6 +184,8 @@
|
||||||
|
|
||||||
<ssl-certificate-settings></ssl-certificate-settings>
|
<ssl-certificate-settings></ssl-certificate-settings>
|
||||||
|
|
||||||
|
<open-amt-settings ng-show="settings.FeatureFlagSettings && settings.FeatureFlagSettings['open-amt']"></open-amt-settings>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
|
|
Loading…
Reference in New Issue