README ¶
Contents
- Overview
- Security
2.1. Server Public Key
2.2. Client Key Pair
2.3. User Identity - Usage
3.1. Limits
3.2. Permits
3.3. Subscriptions
3.4. Messages - Contributing
4.1. Versioning
4.2. Issue Reporting
4.3. Testing
4.3.1. Functional
4.3.2. Performance
4.4. Releasing
1. Overview
Reference Awakari SDK for a Golang client.
2. Security
To secure the Awakari public API usage, the mutual TLS encryption is used together with additional user identity.
2.1. Server Public Key
Used to authenticate the Awakari service by the client. A client should fetch it by a public link TODO.
package main
import (
"github.com/awakari/client-sdk-go/api"
"os"
...
)
func main() {
...
caCrt, err := os.ReadFile("ca.crt")
if err != nil {
panic(err)
}
...
client, err := api.
NewClientBuilder().
...
ServerPublicKey(caCrt).
...
Build()
...
}
2.2. Client Key Pair
Used to authenticate the Group Client. Contains a client's private key and a client's certificate. A client should request Awakari contacts to obtain it.
A client's certificate is used by Awakari to extract the DN value. This value, e.g.
CN=my-service-using-awakari.com
is treated by Awakari as Group Client Identity.
package main
import (
"github.com/awakari/client-sdk-go/api"
"os"
...
)
func main() {
...
clientCrt, err := os.ReadFile("client.crt")
if err != nil {
panic(err)
}
clientKey, err := os.ReadFile("client.key")
if err != nil {
panic(err)
}
...
client, err := api.
NewClientBuilder().
...
ClientKeyPair(clientCrt, clientKey).
...
Build()
...
}
2.3. User Identity
Note:
- Any Group Client can be used by many users.
- Awakari doesn't verify a user identity and trusts any user id specified by the client.
A client is required to specify a user id in every API call. The user authentication and authorization are the client's
responsibility. The good example is to integrate a 3-rd party identity provider and use the sub
field from a JWT token
as a user id.
3. Usage
See the int_test.go for the complete test code example.
Before using the API, it's necessary to initialize the client:
package main
import (
"github.com/awakari/client-sdk-go/api"
"os"
...
)
func main() {
...
caCrt, err := os.ReadFile("ca.crt")
if err != nil {
panic(err)
}
clientCrt, err := os.ReadFile("client.crt")
if err != nil {
panic(err)
}
clientKey, err := os.ReadFile("client.key")
if err != nil {
panic(err)
}
client, err := api.
NewClientBuilder().
ServerPublicKey(caCrt).
ClientKeyPair(clientCrt, clientKey).
ApiUri("awakari.com:443").
Build()
if err != nil {
panic(err)
}
defer client.Close()
...
}
3.1. Limits
Usage limit represents the successful API call count limit. The limit is identified per:
- group id
- user id (optional)
- subject
There are the group-level limits where user id is not specified. All users from the group share the group limit in this case.
Usage subject may be one of:
- Publish Messages
- Subscriptions
package main
import (
"context"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"time"
...
)
func main() {
...
var client api.Client // initialize client
var userId string // set this to "sub" field value from an authentication token, for example
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var l usage.Limit
var err error
l, err = client.ReadUsageLimit(ctx, userId, usage.SubjectPublishMessages)
if err == nil {
if u.UserId == "" {
fmt.Printf("group usage publish messages limit: %d", l.Count)
} else {
fmt.Printf("user specific publish messages limit: %d", l.Count)
}
}
...
}
3.2. Permits
Usage permits represents the current usage statistics (counters) by the subject. Similar to usage limit, the counters represent the group-level usage when the user id is empty.
package main
import (
"context"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"time"
...
)
func main() {
...
var client api.Client // initialize client
var userId string // set this to "sub" field value from an authentication token, for example
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var u usage.Usage
var err error
u, err = client.ReadUsage(ctx, userId, usage.SubjectSubscriptions)
if err == nil {
if u.UserId == "" {
fmt.Printf("group subscriptions usage: %d", l.Count)
} else {
fmt.Printf("user specific subscriptions usage: %d", l.Count)
}
}
...
}
3.3. Subscriptions
package main
import (
"context"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"time"
...
)
func main() {
...
var client api.Client // initialize client
var userId string // set this to "sub" field value from an authentication token, for example
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Minute)
defer cancel()
// Create a subscription
var subId string
var err error
subData := subscription.Data{
Metadata: subscription.Metadata{
Description: "my subscription",
Enabled: true,
},
Condition: condition.NewBuilder().
MatchAttrKey("tags").
MatchAttrValuePattern("SpaceX").
MatchAttrValuePartial().
BuildKiwiTreeCondition(),
}
subId, err = client.CreateSubscription(ctx, userId, subData)
// Update the subscription mutable fields
md := subscription.Metadata{
Description: "my disabled subscription",
Enabled: false,
}
err = client.UpdateSubscriptionMetadata(ctx, userId, subId, md)
// Delete the subscription
err = client.DeleteSubscription(ctx, userId, subId)
if err != nil {
panic(err)
}
// Search own subscription ids
var ids []string
limit := uint32(10)
ids, err = client.Search(ctx, userId, limit, "")
if err != nil {
panic(err)
}
for _, id := range ids {
// Read the subscription details
subData, err = client.Read(ctx, userId, id)
if err == nil {
panic(err)
}
fmt.Printf("subscription %d details: %+v\n", id, subData)
}
...
}
3.4. Messages
3.4.1. Publishing
package main
import (
"context"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"github.com/cloudevents/sdk-go/binding/format/protobuf/v2/pb"
"time"
)
...
)
func main() {
...
var client api.Client // initialize client
var userId string // set this to "sub" field value from an authentication token, for example
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var ws model.WriteStream[*pb.CloudEvent]
ws, err = client.WriteMessages(ctx, userId)
if err == nil {
panic(err)
}
defer ws.Close()
msgs := []*pb.CloudEvent{
{
Id: uuid.NewString(),
Source: "http://arxiv.org/abs/2305.06364",
SpecVersion: "1.0",
Type: "com.github.awakari.producer-rss",
Attributes: map[string]*pb.CloudEventAttributeValue{
"summary": {
Attr: &pb.CloudEventAttributeValue_CeString{
CeString: "<p>We propose that the dark matter of our universe could be sterile neutrinos which reside within the twin sector of a mirror twin Higgs model. In our scenario, these particles are produced through a version of the Dodelson-Widrow mechanism that takes place entirely within the twin sector, yielding a dark matter candidate that is consistent with X-ray and gamma-ray line constraints. Furthermore, this scenario can naturally avoid the cosmological problems that are typically encountered in mirror twin Higgs models. In particular, if the sterile neutrinos in the Standard Model sector decay out of equilibrium, they can heat the Standard Model bath and reduce the contributions of the twin particles to $N_\\mathrm{eff}$. Such decays also reduce the effective temperature of the dark matter, thereby relaxing constraints from large-scale structure. The sterile neutrinos included in this model are compatible with the seesaw mechanism for generating Standard Model neutrino masses. </p> ",
},
},
"tags": {
Attr: &pb.CloudEventAttributeValue_CeString{
CeString: "neutrino dark matter cosmology higgs standard model dodelson-widrow",
},
},
"title": {
Attr: &pb.CloudEventAttributeValue_CeString{
CeString: "Twin Sterile Neutrino Dark Matter. (arXiv:2305.06364v1 [hep-ph])",
},
},
},
Data: &pb.CloudEvent_TextData{
TextData: "",
},
},
}
var writtenCount uint32
var n uint32
for writtenCount < uint32(len(msgs)) {
n, err = ws.WriteBatch(msgs)
if err != nil {
break
}
writtenCount += n
}
if err != nil {
panic(err)
}
...
}
3.4.2. Receiving
package main
import (
"context"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"time"
...
)
func main() {
...
var client api.Client // initialize client
var userId string // set this to "sub" field value from an authentication token, for example
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var rs model.ReadStream[*pb.CloudEvent]
rs, err = client.ReadMessages(ctx, userId, subId)
if err != nil {
panic(err)
}
defer rs.Close()
var msg *pb.CloudEvent
for {
msg, err = rs.Read()
if err != nil {
break
}
fmt.Printf("subscription %s - received the next message: %+v\n", subId, msg)
}
if err != nil {
panic(err)
}
...
}
4. Contributing
4.1. Versioning
The library follows the semantic versioning. The single source of the version info is the git tag:
git describe --tags --abbrev=0
4.2. Issue Reporting
TODO
4.3. Testing
4.3.1. Functional
API_URI=api.local:443 \
CLIENT_CERT_PATH=test0.client0.crt \
CLIENT_PRIVATE_KEY_PATH=test0.client0.key \
SERVER_PUBLIC_KEY_PATH=ca.crt \
make test
4.3.2. Performance
TODO
4.4. Releasing
To release a new version (e.g. 1.2.3
) it's enough to put a git tag:
git tag -v1.2.3
git push --tags
The corresponding CI job is started to build a docker image and push it with the specified tag (+latest).