- Overview
- Security
2.1. Certificate Authority
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
Reference Awakari SDK for a Golang client.
To secure the Awakari public API usage, the mutual TLS encryption is used together with additional user identity.
Note:
Not available for self-hosted core system. Skip the 2. Security section entirely when using self-hosted core system.
Used to authenticate the Awakari service by the client. A client should fetch it, for example: demo instance CA.
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().
...
CertAuthority(caCrt).
...
Build()
...
}
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()
...
}
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.
See the int_test.go for the complete test code example.
Before using the API, it's necessary to initialize the client. When using a hybrid deployment the initialization should be like follows:
package main
import (
"github.com/awakari/client-sdk-go/api"
"os"
...
)
func main() {
...
client, err := api.
NewClientBuilder().
ReaderUri("core-reader:50051"). // skip this line if reader API is not used
SubscriptionsUri("core-subscriptionsproxy:50051"). // skip this line if subscriptions API is not used
WriterUri("core-resolver:50051"). // skip this line if writer API is not used
Build()
if err != nil {
panic(err)
}
defer client.Close()
...
}
The initialization is a bit different for a serverless API usage:
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().
CertAuthority(caCrt).
ClientKeyPair(clientCrt, clientKey).
ApiUri("demo.awakari.com:443").
Build()
if err != nil {
panic(err)
}
defer client.Close()
...
}
Note:
Limits API is not available for self-hosted core system. Skip the 3.1. Limits section entirely when using self-hosted core system.
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:
- Subscriptions
- Publish Events
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 // TODO initialize client here
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.SubjectPublishEvents)
if err == nil {
if u.UserId == "" {
fmt.Printf("group usage publish events limit: %d", l.Count)
} else {
fmt.Printf("user specific publish events limit: %d", l.Count)
}
}
...
}
Note:
Permits API is not available for self-hosted core system. Skip the 3.2. Permits section entirely when using self-hosted core system.
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 // TODO initialize client here
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)
}
}
...
}
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 // TODO initialize client here
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{
Description: "my subscription",
Enabled: true,
Expires: time.Now().Add(24 * time.Hour), // optional, subscription will be treated as disabled after it expires
Condition: condition.NewBuilder().
AttributeKey("tags").
AnyOfWords("SpaceX").
BuildTextCondition(),
}
subId, err = client.CreateSubscription(ctx, userId, subData)
// Update the subscription
upd := subscription.Data{
Description: "my disabled subscription",
Enabled: false,
subData.Expires, // don't change
subData.Condition, // don't change
}
err = client.UpdateSubscription(ctx, userId, subId, upd)
// 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)
}
...
}
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 // TODO initialize client here
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.OpenMessagesWriter(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)
}
...
}
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 // TODO initialize client here
var userId string // set this to "sub" field value from an authentication token, for example
batchSize := uint32(16)
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var r model.ReadStream[*pb.CloudEvent]
r, err = client.OpenMessagesReader(ctx, userId, subId, batchSize)
if err != nil {
panic(err)
}
defer r.Close()
var msgs []*pb.CloudEvent
for {
msgs, err = r.Read()
if err != nil {
break
}
fmt.Printf("subscription %s - received the next messages batch: %+v\n", subId, msgs)
}
if err != nil {
panic(err)
}
...
}
package main
import (
"context"
"errors"
"fmt"
"github.com/awakari/client-sdk-go/api"
"github.com/awakari/client-sdk-go/model/usage"
"time"
)
...
)
func main() {
...
var client api.Client // TODO initialize client here
var userId string // set this to "sub" field value from an authentication token, for example
batchSize := uint32(16)
...
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
var r model.ReadStream[*pb.CloudEvent]
r, err = client.OpenMessagesAckReader(ctx, userId, subId, batchSize)
if err != nil {
panic(err)
}
defer r.Close()
var msgs []*pb.CloudEvent
var ackCount uint32
for {
msgs, err = r.Read()
if err == nil {
fmt.Printf("subscription %s - received the next messages batch: %+v\n", subId, msgs)
ackCount, err = process(msgs)
}
if ackCount > 0 {
err = errors.Join(err, r.Ack(ackCount))
}
if err != nil {
break
}
}
if err != nil {
panic(err)
}
...
}
The library follows the semantic versioning. The single source of the version info is the git tag:
git describe --tags --abbrev=0
TODO
API_URI=api.local:443 \
CA_PATH=ca.crt \
CLIENT_CERT_PATH=test0.client0.crt \
CLIENT_PRIVATE_KEY_PATH=test0.client0.key \
make test
TODO
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).