Over the last few years, I’ve been building trading strategies and outputting results to ElasticSearch. I wanted to view my results on the go and have observability over my infrastructure. While Kibana (and OpenDashboard) are excellent tools, they cater to a full desktop experience with all the bells and whistles.

So I made Search Ops, a mobile app that enables you to query and view your data from ElasticSearch.

Feel free to read the entire post, or jump to the latest release notes.

Features

The app allows you to query ElasticSearch and OpenSearch clusters using Free Text String, with compounds (AND/OR) and with Date Ranges (if you have a defined a Date type in your index). Queries are saved, so you can easily switch between hosts and indexes, reusing queries whereever you need to.

The application support ElasticSearch (v5.0 and above) and OpenSearch (v1.0 and above)

Connections can be made using a CloudID from Elastic.co or a direct host connection (HTTP/HTTPS), with a range of authentication methods.

  • Username/Password
  • Auth Token
  • API Token
  • API Key

The application provides a read only access to your Search clusters, and as a result only needs a user with Viewer and Monitor User.

Business Logic

The application is split into two parts, the Business Logic (a Swift Package) and a Presentation Layer using SwiftUI. The Business Logic is public and available on Github to view. The app uses a local on device open source database called Realm, by MongoDB foundation with encyrption on.

The Business Logic is available to view on Github

Encryption

Search Ops uses Realm as local on device database with encyrption turned on. You can see the full implementation here, SearchOps/Sources/SearchOps/DataManagers/RealmManager.swift.

The RealmManager Class generates a key and saves it to the local keychain. This keychain does not persist to iCloud.

1
2
3
4
5
6
7
8
private static func getKey() throws -> Data {
    // generate key
    var key = Data(count: 64)
    try key.withUnsafeMutableBytes({ (pointer: UnsafeMutableRawBufferPointer) in
        let result = SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!)
    })
    return key
}

The Realm Database is opened with the encryption key thats unique to the local device.

1
2
3
4
private static func getRealmConfig() -> Realm.Configuration { 
    // try to load configuration with key
    return !try Realm.Configuration(encryptionKey: getKey())
}

History and Logs

You can reuse previous queries and view the response of any requests made.

Privacy

Privacy is important and I’ve taken extra steps to build trust, splitting the application up and sharing the business logic.

The Business Logic documents how host credentials are stored locally, and requests are made. The commit hash of the business logic in use is showned in the iOS application on the Settings page. There are also no analytics or tracking within the application too.

Available on Apple’s App Store

Release Notes

I’ve made a website just for the SearchOps app, to showcase new features and publish release notes, see more at https://searchops.app