-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from bbockelm/origin_director_registration
Have origin advertise to the director service
- Loading branch information
Showing
8 changed files
with
304 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
ManagerHost: redirector.osgstorage.org | ||
SummaryMonitoringHost: xrd-report.osgstorage.org | ||
DetailedMonitoringHost: xrd-mon.osgstorage.org | ||
TopologyNamespaceURL: https://topology.opensciencegrid.org/stashcache/namespaces.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package director | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"net/url" | ||
"path" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/jellydator/ttlcache/v3" | ||
"github.com/lestrrat-go/jwx/jwa" | ||
"github.com/lestrrat-go/jwx/jwk" | ||
"github.com/lestrrat-go/jwx/jwt" | ||
"github.com/pelicanplatform/pelican/config" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
type ( | ||
|
||
OriginAdvertise struct { | ||
Name string `json:"name"` | ||
URL string `json:"url"` | ||
Namespaces []NamespaceAd `json:"namespaces"` | ||
} | ||
|
||
) | ||
|
||
var ( | ||
namespaceKeys = ttlcache.New[string, *jwk.AutoRefresh](ttlcache.WithTTL[string, *jwk.AutoRefresh](15 * time.Minute)) | ||
namespaceKeysMutex = sync.RWMutex{} | ||
) | ||
|
||
|
||
func CreateAdvertiseToken(namespace string) (string, error) { | ||
key, err := config.GetOriginJWK() | ||
if err != nil { | ||
return "", err | ||
} | ||
issuer_url, err := GetIssuerURL(namespace) | ||
if err != nil { | ||
return "", err | ||
} | ||
director := viper.GetString("DirectorURL") | ||
if director == "" { | ||
return "", errors.New("Director URL is not known; cannot create advertise token") | ||
} | ||
|
||
tok, err := jwt.NewBuilder(). | ||
Claim("scope", "pelican.advertise"). | ||
Issuer(issuer_url). | ||
Audience([]string{director}). | ||
Subject("origin"). | ||
Expiration(time.Now().Add(time.Minute)). | ||
Build() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
signed, err := jwt.Sign(tok, jwa.ES512, key) | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(signed), nil | ||
} | ||
|
||
// | ||
// Given a token and a location in the namespace to advertise in, | ||
// see if the entity is authorized to advertise an origin for the | ||
// namespace | ||
func VerifyAdvertiseToken(token, namespace string) (bool, error) { | ||
issuer_url, err := GetIssuerURL(namespace) | ||
if err != nil { | ||
return false, err | ||
} | ||
var ar *jwk.AutoRefresh | ||
{ | ||
namespaceKeysMutex.RLock() | ||
defer namespaceKeysMutex.Unlock() | ||
item := namespaceKeys.Get(namespace) | ||
if !item.IsExpired() { | ||
ar = item.Value() | ||
} | ||
} | ||
ctx := context.Background() | ||
if ar == nil { | ||
ar := jwk.NewAutoRefresh(ctx) | ||
ar.Configure(issuer_url, jwk.WithMinRefreshInterval(15 * time.Minute)) | ||
namespaceKeysMutex.Lock() | ||
defer namespaceKeysMutex.Unlock() | ||
namespaceKeys.Set(namespace, ar, ttlcache.DefaultTTL) | ||
} | ||
keyset, err := ar.Fetch(ctx, issuer_url) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
tok, err := jwt.Parse([]byte(token), jwt.WithKeySet(keyset), jwt.WithValidate(true)) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
scope_any, present := tok.Get("scope") | ||
if !present { | ||
return false, errors.New("No scope is present; required to advertise to director") | ||
} | ||
scope, ok := scope_any.(string) | ||
if !ok { | ||
return false, errors.New("scope claim in token is not string-valued") | ||
} | ||
|
||
scopes := strings.Split(scope, " ") | ||
|
||
for _, scope := range(scopes) { | ||
if scope == "pelican.advertise" { | ||
return true, nil | ||
} | ||
} | ||
return false, nil | ||
} | ||
|
||
func GetIssuerURL(prefix string) (string, error) { | ||
namespace_url_string := viper.GetString("NamespaceURL") | ||
if namespace_url_string == "" { | ||
return "", errors.New("Namespace URL is not set") | ||
} | ||
namespace_url, err := url.Parse(namespace_url_string) | ||
if err != nil { | ||
return "", err | ||
} | ||
namespace_url.Path = path.Join(namespace_url.Path, "namespaces", prefix) | ||
return namespace_url.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package origin_ui | ||
|
||
import ( | ||
"bytes" | ||
"crypto/tls" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/pelicanplatform/pelican/director" | ||
"github.com/pkg/errors" | ||
log "github.com/sirupsen/logrus" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
func PeriodicAdvertiseOrigin() error { | ||
ticker := time.NewTicker(1 * time.Minute) | ||
go func() { | ||
err := AdvertiseOrigin() | ||
if err != nil { | ||
log.Warningln("Origin advertise failed:", err) | ||
} | ||
for { | ||
<-ticker.C | ||
err := AdvertiseOrigin() | ||
if err != nil { | ||
log.Warningln("Origin advertise failed:", err) | ||
} | ||
} | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func AdvertiseOrigin() error { | ||
name := viper.GetString("Sitename") | ||
if name == "" { | ||
return errors.New("Origin name isn't set") | ||
} | ||
// TODO: waiting on a different branch to merge origin URL generation | ||
originUrl := "https://localhost:8444" | ||
|
||
ad := director.OriginAdvertise{ | ||
Name: name, | ||
URL: originUrl, | ||
Namespaces: make([]director.NamespaceAd, 0), | ||
} | ||
|
||
body, err := json.Marshal(ad) | ||
if err != nil { | ||
return errors.Wrap(err, "Failed to generate JSON description of origin") | ||
} | ||
|
||
directorUrlStr := viper.GetString("DirectorURL") | ||
if directorUrlStr == "" { | ||
return errors.New("Director endpoint URL is not known") | ||
} | ||
directorUrl, err := url.Parse(directorUrlStr) | ||
if err != nil { | ||
return errors.Wrap(err, "Failed to parse DirectorURL") | ||
} | ||
directorUrl.Path = "/api/v1.0/director/registerOrigin" | ||
|
||
req, err := http.NewRequest("POST", directorUrl.String(), bytes.NewBuffer(body)) | ||
if err != nil { | ||
return errors.Wrap(err, "Failed to create POST request for director registration") | ||
} | ||
|
||
req.Header.Set("Content-Type", "application/json") | ||
|
||
client := http.Client{} | ||
if viper.GetBool("TLSSkipVerify") { | ||
tr := &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | ||
} | ||
client = http.Client{Transport: tr} | ||
} | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return errors.Wrap(err, "Failed to start request for director registration") | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode > 299 { | ||
return fmt.Errorf("Error response %v from director registration: %v", resp.StatusCode, resp.Status) | ||
} | ||
|
||
return nil | ||
} |