From 71201ec903d39daec145f63e12c647c851494e4b Mon Sep 17 00:00:00 2001 From: CTFang Date: Mon, 18 Dec 2023 18:41:32 +0800 Subject: [PATCH] Feature: Add NRF Consumer support OAuth2 (#90) * Feature: NRF consumer support oauth2 * Feature: Consume NRF service via OAuth * Fix: Minor change * Fix: prevent assertion and modify config setting * Fix: move GetTokenCtx() and fix logics --------- Co-authored-by: CTFang@WireLab --- go.mod | 2 +- go.sum | 5 +- internal/context/context.go | 14 ++ internal/context/sm_context.go | 7 +- .../sbi/consumer/{nnrf.go => nf_discovery.go} | 154 ++-------------- internal/sbi/consumer/nf_management.go | 167 ++++++++++++++++++ pkg/factory/config.go | 1 + 7 files changed, 209 insertions(+), 141 deletions(-) rename internal/sbi/consumer/{nnrf.go => nf_discovery.go} (50%) create mode 100644 internal/sbi/consumer/nf_management.go diff --git a/go.mod b/go.mod index e186f7f9..4c0c7dda 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/free5gc/aper v1.0.4 github.com/free5gc/nas v1.1.0 github.com/free5gc/ngap v1.0.6 - github.com/free5gc/openapi v1.0.7-0.20230802173229-2b3ded4db293 + github.com/free5gc/openapi v1.0.7-0.20231216094313-e15a4ff046f6 github.com/free5gc/pfcp v1.0.6 github.com/free5gc/util v1.0.5-0.20231001095115-433858e5be94 github.com/gin-gonic/gin v1.9.1 diff --git a/go.sum b/go.sum index 932c554c..26d9f328 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/free5gc/ngap v1.0.6 h1:f9sKqHMNrFZVo9Kp8hAyrCXSoI8l746N5O+DFn7vKHA= github.com/free5gc/ngap v1.0.6/go.mod h1:TG1kwwU/EyIlJ3bxY591rdxpD5ZeYnLZTzoWjcfvrBM= github.com/free5gc/openapi v1.0.4/go.mod h1:KRCnnp0GeK0Bl4gnrX79cQAidKXNENf8VRdG0y9R0Fc= github.com/free5gc/openapi v1.0.6/go.mod h1:iw/N0E+FlX44EEx24IBi2EdZW8v+bkj3ETWPGnlK9DI= -github.com/free5gc/openapi v1.0.7-0.20230802173229-2b3ded4db293 h1:BSIvKCYu7646sE8J9R1L8v2R435otUik3wOFN33csfs= -github.com/free5gc/openapi v1.0.7-0.20230802173229-2b3ded4db293/go.mod h1:iw/N0E+FlX44EEx24IBi2EdZW8v+bkj3ETWPGnlK9DI= +github.com/free5gc/openapi v1.0.7-0.20231216094313-e15a4ff046f6 h1:8P/wOkTAQMgZJe9pUUNSTE5PWeAdlMrsU9kLsI+VAVE= +github.com/free5gc/openapi v1.0.7-0.20231216094313-e15a4ff046f6/go.mod h1:qv9KqEucoZSeENPRFGxfTe+33ZWYyiYFx1Rj+H0DoWA= github.com/free5gc/pfcp v1.0.6 h1:dKEVyZWozF1G+yk1JXw/1ggtIRI0v362say/Q6VDZTE= github.com/free5gc/pfcp v1.0.6/go.mod h1:WzpW7Zxhx5WONMumNKRWbPn7pl/iTYp2FqRLNiOWUjs= github.com/free5gc/tlv v1.0.2-0.20230131124215-8b6ebd69bf93 h1:QPSSI5zw4goiIfxem9doVyMqTO8iKLQ536pzpET5Y+Q= @@ -546,6 +546,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/context/context.go b/internal/context/context.go index 6d98e63a..0282cfb8 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -14,6 +14,7 @@ import ( "github.com/free5gc/openapi/Nnrf_NFManagement" "github.com/free5gc/openapi/Nudm_SubscriberDataManagement" "github.com/free5gc/openapi/models" + "github.com/free5gc/openapi/oauth" "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" "github.com/free5gc/smf/pkg/factory" @@ -48,12 +49,14 @@ type SMFContext struct { SnssaiInfos []*SnssaiSmfInfo NrfUri string + NrfCertPem string NFManagementClient *Nnrf_NFManagement.APIClient NFDiscoveryClient *Nnrf_NFDiscovery.APIClient SubscriberDataManagementClient *Nudm_SubscriberDataManagement.APIClient Locality string AssocFailAlertInterval time.Duration AssocFailRetryInterval time.Duration + OAuth2Required bool UserPlaneInformation *UserPlaneInformation Ctx context.Context @@ -152,6 +155,7 @@ func InitSmfContext(config *factory.Config) { logger.CtxLog.Warn("NRF Uri is empty! Using localhost as NRF IPv4 address.") smfContext.NrfUri = fmt.Sprintf("%s://%s:%d", smfContext.URIScheme, "127.0.0.1", 29510) } + smfContext.NrfCertPem = configuration.NrfCertPem if pfcp := configuration.PFCP; pfcp != nil { smfContext.ListenAddr = pfcp.ListenAddr @@ -283,3 +287,13 @@ func GetUserPlaneInformation() *UserPlaneInformation { func GetUEDefaultPathPool(groupName string) *UEDefaultPaths { return smfContext.UEDefaultPathPool[groupName] } + +func (c *SMFContext) GetTokenCtx(scope, targetNF string) ( + context.Context, *models.ProblemDetails, error, +) { + if !c.OAuth2Required { + return context.TODO(), nil, nil + } + return oauth.GetTokenCtx(models.NfType_SMF, + c.NfInstanceID, c.NrfUri, scope, targetNF) +} diff --git a/internal/context/sm_context.go b/internal/context/sm_context.go index 4774cb24..308337d0 100644 --- a/internal/context/sm_context.go +++ b/internal/context/sm_context.go @@ -1,7 +1,6 @@ package context import ( - "context" "fmt" "math" "net" @@ -410,6 +409,10 @@ func (smContext *SMContext) PDUAddressToNAS() ([12]byte, uint8) { // PCFSelection will select PCF for this SM Context func (smContext *SMContext) PCFSelection() error { + ctx, _, err := GetSelf().GetTokenCtx("nnrf-disc", "NRF") + if err != nil { + return err + } // Send NFDiscovery for find PCF localVarOptionals := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{} @@ -420,7 +423,7 @@ func (smContext *SMContext) PCFSelection() error { rep, res, err := GetSelf(). NFDiscoveryClient. NFInstancesStoreApi. - SearchNFInstances(context.TODO(), models.NfType_PCF, models.NfType_SMF, &localVarOptionals) + SearchNFInstances(ctx, models.NfType_PCF, models.NfType_SMF, &localVarOptionals) if err != nil { return err } diff --git a/internal/sbi/consumer/nnrf.go b/internal/sbi/consumer/nf_discovery.go similarity index 50% rename from internal/sbi/consumer/nnrf.go rename to internal/sbi/consumer/nf_discovery.go index 484fd03b..c671d534 100644 --- a/internal/sbi/consumer/nnrf.go +++ b/internal/sbi/consumer/nf_discovery.go @@ -1,11 +1,7 @@ package consumer import ( - "context" - "fmt" "net/http" - "strings" - "time" "github.com/antihax/optional" "github.com/mohae/deepcopy" @@ -18,115 +14,19 @@ import ( "github.com/free5gc/smf/internal/logger" ) -func SendNFRegistration() error { - smfProfile := smf_context.NFProfile - - sNssais := []models.Snssai{} - for _, snssaiSmfInfo := range *smfProfile.SMFInfo.SNssaiSmfInfoList { - sNssais = append(sNssais, *snssaiSmfInfo.SNssai) - } - - // set nfProfile - profile := models.NfProfile{ - NfInstanceId: smf_context.GetSelf().NfInstanceID, - NfType: models.NfType_SMF, - NfStatus: models.NfStatus_REGISTERED, - Ipv4Addresses: []string{smf_context.GetSelf().RegisterIPv4}, - NfServices: smfProfile.NFServices, - SmfInfo: smfProfile.SMFInfo, - SNssais: &sNssais, - PlmnList: smfProfile.PLMNList, - } - if smf_context.GetSelf().Locality != "" { - profile.Locality = smf_context.GetSelf().Locality - } - var rep models.NfProfile - var res *http.Response - var err error - - // Check data (Use RESTful PUT) - for { - rep, res, err = smf_context.GetSelf(). - NFManagementClient. - NFInstanceIDDocumentApi. - RegisterNFInstance(context.TODO(), smf_context.GetSelf().NfInstanceID, profile) - if err != nil || res == nil { - logger.ConsumerLog.Infof("SMF register to NRF Error[%s]", err.Error()) - time.Sleep(2 * time.Second) - continue - } - defer func() { - if resCloseErr := res.Body.Close(); resCloseErr != nil { - logger.ConsumerLog.Errorf("RegisterNFInstance response body cannot close: %+v", resCloseErr) - } - }() - - status := res.StatusCode - if status == http.StatusOK { - // NFUpdate - break - } else if status == http.StatusCreated { - // NFRegister - resourceUri := res.Header.Get("Location") - // resouceNrfUri := resourceUri[strings.LastIndex(resourceUri, "/"):] - smf_context.GetSelf().NfInstanceID = resourceUri[strings.LastIndex(resourceUri, "/")+1:] - break - } else { - logger.ConsumerLog.Infof("handler returned wrong status code %d", status) - // fmt.Errorf("NRF return wrong status code %d", status) - } - } - - logger.InitLog.Infof("SMF Registration to NRF %v", rep) - return nil -} - -func RetrySendNFRegistration(MaxRetry int) error { - retryCount := 0 - for retryCount < MaxRetry { - err := SendNFRegistration() - if err == nil { - return nil - } - logger.ConsumerLog.Warnf("Send NFRegistration Failed by %v", err) - retryCount++ - } - - return fmt.Errorf("[SMF] Retry NF Registration has meet maximum") -} - -func SendNFDeregistration() error { - // Check data (Use RESTful DELETE) - res, localErr := smf_context.GetSelf(). - NFManagementClient. - NFInstanceIDDocumentApi. - DeregisterNFInstance(context.TODO(), smf_context.GetSelf().NfInstanceID) - if localErr != nil { - logger.ConsumerLog.Warnln(localErr) - return localErr - } - defer func() { - if resCloseErr := res.Body.Close(); resCloseErr != nil { - logger.ConsumerLog.Errorf("DeregisterNFInstance response body cannot close: %+v", resCloseErr) - } - }() - if res != nil { - if status := res.StatusCode; status != http.StatusNoContent { - logger.ConsumerLog.Warnln("handler returned wrong status code ", status) - return openapi.ReportError("handler returned wrong status code %d", status) - } +func SendNFDiscoveryUDM() (*models.ProblemDetails, error) { + ctx, pd, err := smf_context.GetSelf().GetTokenCtx("nnrf-disc", "NRF") + if err != nil { + return pd, err } - return nil -} -func SendNFDiscoveryUDM() (*models.ProblemDetails, error) { localVarOptionals := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{} // Check data result, httpResp, localErr := smf_context.GetSelf(). NFDiscoveryClient. NFInstancesStoreApi. - SearchNFInstances(context.TODO(), models.NfType_UDM, models.NfType_SMF, &localVarOptionals) + SearchNFInstances(ctx, models.NfType_UDM, models.NfType_SMF, &localVarOptionals) if localErr == nil { smf_context.GetSelf().UDMProfile = result.NfInstances[0] @@ -161,6 +61,11 @@ func SendNFDiscoveryUDM() (*models.ProblemDetails, error) { } func SendNFDiscoveryPCF() (problemDetails *models.ProblemDetails, err error) { + ctx, pd, err := smf_context.GetSelf().GetTokenCtx("nnrf-disc", "NRF") + if err != nil { + return pd, err + } + // Set targetNfType targetNfType := models.NfType_PCF // Set requestNfType @@ -171,7 +76,7 @@ func SendNFDiscoveryPCF() (problemDetails *models.ProblemDetails, err error) { result, httpResp, localErr := smf_context.GetSelf(). NFDiscoveryClient. NFInstancesStoreApi. - SearchNFInstances(context.TODO(), targetNfType, requesterNfType, &localVarOptionals) + SearchNFInstances(ctx, targetNfType, requesterNfType, &localVarOptionals) if localErr == nil { logger.ConsumerLog.Traceln(result.NfInstances) @@ -184,7 +89,7 @@ func SendNFDiscoveryPCF() (problemDetails *models.ProblemDetails, err error) { logger.ConsumerLog.Warnln("handler returned wrong status code ", httpResp.Status) if httpResp.Status != localErr.Error() { err = localErr - return + return problemDetails, err } problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) problemDetails = &problem @@ -196,6 +101,11 @@ func SendNFDiscoveryPCF() (problemDetails *models.ProblemDetails, err error) { } func SendNFDiscoveryServingAMF(smContext *smf_context.SMContext) (*models.ProblemDetails, error) { + ctx, pd, err := smf_context.GetSelf().GetTokenCtx("nnrf-disc", "NRF") + if err != nil { + return pd, err + } + targetNfType := models.NfType_AMF requesterNfType := models.NfType_SMF @@ -207,7 +117,7 @@ func SendNFDiscoveryServingAMF(smContext *smf_context.SMContext) (*models.Proble result, httpResp, localErr := smf_context.GetSelf(). NFDiscoveryClient. NFInstancesStoreApi. - SearchNFInstances(context.TODO(), targetNfType, requesterNfType, &localVarOptionals) + SearchNFInstances(ctx, targetNfType, requesterNfType, &localVarOptionals) if localErr == nil { if result.NfInstances == nil { @@ -236,31 +146,3 @@ func SendNFDiscoveryServingAMF(smContext *smf_context.SMContext) (*models.Proble return nil, nil } - -func SendDeregisterNFInstance() (*models.ProblemDetails, error) { - logger.ConsumerLog.Infof("Send Deregister NFInstance") - - smfSelf := smf_context.GetSelf() - // Set client and set url - - res, err := smfSelf. - NFManagementClient. - NFInstanceIDDocumentApi. - DeregisterNFInstance(context.Background(), smfSelf.NfInstanceID) - if err == nil { - return nil, err - } else if res != nil { - defer func() { - if resCloseErr := res.Body.Close(); resCloseErr != nil { - logger.ConsumerLog.Errorf("DeregisterNFInstance response body cannot close: %+v", resCloseErr) - } - }() - if res.Status != err.Error() { - return nil, err - } - problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) - return &problem, err - } else { - return nil, openapi.ReportError("server no response") - } -} diff --git a/internal/sbi/consumer/nf_management.go b/internal/sbi/consumer/nf_management.go new file mode 100644 index 00000000..7f85b37a --- /dev/null +++ b/internal/sbi/consumer/nf_management.go @@ -0,0 +1,167 @@ +package consumer + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + smf_context "github.com/free5gc/smf/internal/context" + "github.com/free5gc/smf/internal/logger" +) + +func SendNFRegistration() error { + smfProfile := smf_context.NFProfile + + sNssais := []models.Snssai{} + for _, snssaiSmfInfo := range *smfProfile.SMFInfo.SNssaiSmfInfoList { + sNssais = append(sNssais, *snssaiSmfInfo.SNssai) + } + + // set nfProfile + profile := models.NfProfile{ + NfInstanceId: smf_context.GetSelf().NfInstanceID, + NfType: models.NfType_SMF, + NfStatus: models.NfStatus_REGISTERED, + Ipv4Addresses: []string{smf_context.GetSelf().RegisterIPv4}, + NfServices: smfProfile.NFServices, + SmfInfo: smfProfile.SMFInfo, + SNssais: &sNssais, + PlmnList: smfProfile.PLMNList, + } + if smf_context.GetSelf().Locality != "" { + profile.Locality = smf_context.GetSelf().Locality + } + var nf models.NfProfile + var res *http.Response + var err error + + // Check data (Use RESTful PUT) + for { + nf, res, err = smf_context.GetSelf(). + NFManagementClient. + NFInstanceIDDocumentApi. + RegisterNFInstance(context.TODO(), smf_context.GetSelf().NfInstanceID, profile) + if err != nil || res == nil { + logger.ConsumerLog.Infof("SMF register to NRF Error[%s]", err.Error()) + time.Sleep(2 * time.Second) + continue + } + defer func() { + if resCloseErr := res.Body.Close(); resCloseErr != nil { + logger.ConsumerLog.Errorf("RegisterNFInstance response body cannot close: %+v", resCloseErr) + } + }() + + status := res.StatusCode + if status == http.StatusOK { + // NFUpdate + break + } else if status == http.StatusCreated { + // NFRegister + resourceUri := res.Header.Get("Location") + // resouceNrfUri := resourceUri[strings.LastIndex(resourceUri, "/"):] + smf_context.GetSelf().NfInstanceID = resourceUri[strings.LastIndex(resourceUri, "/")+1:] + + oauth2 := false + if nf.CustomInfo != nil { + v, ok := nf.CustomInfo["oauth2"].(bool) + if ok { + oauth2 = v + logger.MainLog.Infoln("OAuth2 setting receive from NRF:", oauth2) + } + } + smf_context.GetSelf().OAuth2Required = oauth2 + if oauth2 && smf_context.GetSelf().NrfCertPem == "" { + logger.CfgLog.Error("OAuth2 enable but no nrfCertPem provided in config.") + } + break + } else { + logger.ConsumerLog.Infof("handler returned wrong status code %d", status) + // fmt.Errorf("NRF return wrong status code %d", status) + } + } + + logger.InitLog.Infof("SMF Registration to NRF %v", nf) + return nil +} + +func RetrySendNFRegistration(MaxRetry int) error { + retryCount := 0 + for retryCount < MaxRetry { + err := SendNFRegistration() + if err == nil { + return nil + } + logger.ConsumerLog.Warnf("Send NFRegistration Failed by %v", err) + retryCount++ + } + + return fmt.Errorf("[SMF] Retry NF Registration has meet maximum") +} + +func SendNFDeregistration() error { + // Check data (Use RESTful DELETE) + + ctx, _, err := smf_context.GetSelf().GetTokenCtx("nnrf-nfm", "NRF") + if err != nil { + return err + } + + res, localErr := smf_context.GetSelf(). + NFManagementClient. + NFInstanceIDDocumentApi. + DeregisterNFInstance(ctx, smf_context.GetSelf().NfInstanceID) + if localErr != nil { + logger.ConsumerLog.Warnln(localErr) + return localErr + } + defer func() { + if resCloseErr := res.Body.Close(); resCloseErr != nil { + logger.ConsumerLog.Errorf("DeregisterNFInstance response body cannot close: %+v", resCloseErr) + } + }() + if res != nil { + if status := res.StatusCode; status != http.StatusNoContent { + logger.ConsumerLog.Warnln("handler returned wrong status code ", status) + return openapi.ReportError("handler returned wrong status code %d", status) + } + } + return nil +} + +func SendDeregisterNFInstance() (*models.ProblemDetails, error) { + logger.ConsumerLog.Infof("Send Deregister NFInstance") + + ctx, pd, err := smf_context.GetSelf().GetTokenCtx("nnrf-nfm", "NRF") + if err != nil { + return pd, err + } + + smfSelf := smf_context.GetSelf() + // Set client and set url + + res, err := smfSelf. + NFManagementClient. + NFInstanceIDDocumentApi. + DeregisterNFInstance(ctx, smfSelf.NfInstanceID) + if err == nil { + return nil, err + } else if res != nil { + defer func() { + if resCloseErr := res.Body.Close(); resCloseErr != nil { + logger.ConsumerLog.Errorf("DeregisterNFInstance response body cannot close: %+v", resCloseErr) + } + }() + if res.Status != err.Error() { + return nil, err + } + problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + return &problem, err + } else { + return nil, openapi.ReportError("server no response") + } +} diff --git a/pkg/factory/config.go b/pkg/factory/config.go index b701cc4f..8d4a23cd 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -78,6 +78,7 @@ type Configuration struct { Sbi *Sbi `yaml:"sbi" valid:"required"` PFCP *PFCP `yaml:"pfcp" valid:"required"` NrfUri string `yaml:"nrfUri" valid:"url,required"` + NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` UserPlaneInformation UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` ServiceNameList []string `yaml:"serviceNameList" valid:"required"` SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"`